From 0f8691a48869932cd3de5195f5211c25e4691b21 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Thu, 14 Nov 2019 10:07:01 +0100 Subject: Issue #1288 - Part 4: Update the OpenType Sanitizer component to 8.0.0 --- gfx/ots/LICENSE | 54 +- gfx/ots/README.mozilla | 5 +- gfx/ots/include/opentype-sanitiser.h | 36 +- gfx/ots/include/ots-memory-stream.h | 2 +- gfx/ots/ots-lz4.patch | 74 ++ gfx/ots/ots-visibility.patch | 19 +- gfx/ots/src/avar.cc | 109 ++ gfx/ots/src/avar.h | 42 + gfx/ots/src/cff.cc | 1377 +++++++++++++++---------- gfx/ots/src/cff.h | 61 +- gfx/ots/src/cff_charstring.cc | 1018 ++++++++++++++++++ gfx/ots/src/cff_charstring.h | 103 ++ gfx/ots/src/cff_type2_charstring.cc | 912 ----------------- gfx/ots/src/cff_type2_charstring.h | 101 -- gfx/ots/src/cmap.cc | 342 +++---- gfx/ots/src/cmap.h | 21 +- gfx/ots/src/cvar.cc | 56 + gfx/ots/src/cvar.h | 31 + gfx/ots/src/cvt.cc | 47 +- gfx/ots/src/cvt.h | 13 +- gfx/ots/src/feat.cc | 193 ++++ gfx/ots/src/feat.h | 61 ++ gfx/ots/src/fpgm.cc | 43 +- gfx/ots/src/fpgm.h | 13 +- gfx/ots/src/fvar.cc | 164 +++ gfx/ots/src/fvar.h | 63 ++ gfx/ots/src/gasp.cc | 85 +- gfx/ots/src/gasp.h | 12 +- gfx/ots/src/gdef.cc | 269 +++-- gfx/ots/src/gdef.h | 34 +- gfx/ots/src/glat.cc | 459 +++++++++ gfx/ots/src/glat.h | 172 ++++ gfx/ots/src/gloc.cc | 108 ++ gfx/ots/src/gloc.h | 36 + gfx/ots/src/glyf.cc | 328 +++--- gfx/ots/src/glyf.h | 22 +- gfx/ots/src/gpos.cc | 277 ++--- gfx/ots/src/gpos.h | 22 +- gfx/ots/src/graphite.h | 96 ++ gfx/ots/src/gsub.cc | 209 ++-- gfx/ots/src/gsub.h | 22 +- gfx/ots/src/gvar.cc | 158 +++ gfx/ots/src/gvar.h | 31 + gfx/ots/src/hdmx.cc | 125 +-- gfx/ots/src/hdmx.h | 13 +- gfx/ots/src/head.cc | 139 +-- gfx/ots/src/head.h | 13 +- gfx/ots/src/hhea.cc | 44 +- gfx/ots/src/hhea.h | 11 +- gfx/ots/src/hmtx.cc | 43 +- gfx/ots/src/hmtx.h | 12 +- gfx/ots/src/hvar.cc | 85 ++ gfx/ots/src/hvar.h | 31 + gfx/ots/src/kern.cc | 131 +-- gfx/ots/src/kern.h | 13 +- gfx/ots/src/layout.cc | 249 ++++- gfx/ots/src/layout.h | 21 +- gfx/ots/src/loca.cc | 72 +- gfx/ots/src/loca.h | 11 +- gfx/ots/src/ltsh.cc | 78 +- gfx/ots/src/ltsh.h | 13 +- gfx/ots/src/math.cc | 209 ++-- gfx/ots/src/math_.h | 60 +- gfx/ots/src/maxp.cc | 128 +-- gfx/ots/src/maxp.h | 11 +- gfx/ots/src/metrics.cc | 180 ++-- gfx/ots/src/metrics.h | 40 +- gfx/ots/src/moz.build | 47 +- gfx/ots/src/mvar.cc | 105 ++ gfx/ots/src/mvar.h | 31 + gfx/ots/src/name.cc | 150 +-- gfx/ots/src/name.h | 15 +- gfx/ots/src/os2.cc | 384 ++++--- gfx/ots/src/os2.h | 15 +- gfx/ots/src/ots.cc | 628 ++++++++---- gfx/ots/src/ots.h | 220 ++-- gfx/ots/src/post.cc | 145 ++- gfx/ots/src/post.h | 12 +- gfx/ots/src/prep.cc | 44 +- gfx/ots/src/prep.h | 17 +- gfx/ots/src/sile.cc | 74 ++ gfx/ots/src/sile.h | 36 + gfx/ots/src/silf.cc | 977 ++++++++++++++++++ gfx/ots/src/silf.h | 196 ++++ gfx/ots/src/sill.cc | 159 +++ gfx/ots/src/sill.h | 53 + gfx/ots/src/stat.cc | 347 +++++++ gfx/ots/src/stat.h | 155 +++ gfx/ots/src/variations.cc | 261 +++++ gfx/ots/src/variations.h | 26 + gfx/ots/src/vdmx.cc | 125 +-- gfx/ots/src/vdmx.h | 13 +- gfx/ots/src/vhea.cc | 49 +- gfx/ots/src/vhea.h | 12 +- gfx/ots/src/vmtx.cc | 46 +- gfx/ots/src/vmtx.h | 13 +- gfx/ots/src/vorg.cc | 82 +- gfx/ots/src/vorg.h | 13 +- gfx/ots/src/vvar.cc | 95 ++ gfx/ots/src/vvar.h | 31 + gfx/ots/sync.sh | 11 +- gfx/ots/tests/cff_charstring_test.cc | 1588 +++++++++++++++++++++++++++++ gfx/ots/tests/layout_common_table_test.cc | 770 ++++++++++++++ 103 files changed, 11824 insertions(+), 4153 deletions(-) create mode 100644 gfx/ots/ots-lz4.patch create mode 100644 gfx/ots/src/avar.cc create mode 100644 gfx/ots/src/avar.h create mode 100644 gfx/ots/src/cff_charstring.cc create mode 100644 gfx/ots/src/cff_charstring.h delete mode 100644 gfx/ots/src/cff_type2_charstring.cc delete mode 100644 gfx/ots/src/cff_type2_charstring.h create mode 100644 gfx/ots/src/cvar.cc create mode 100644 gfx/ots/src/cvar.h create mode 100644 gfx/ots/src/feat.cc create mode 100644 gfx/ots/src/feat.h create mode 100644 gfx/ots/src/fvar.cc create mode 100644 gfx/ots/src/fvar.h create mode 100644 gfx/ots/src/glat.cc create mode 100644 gfx/ots/src/glat.h create mode 100644 gfx/ots/src/gloc.cc create mode 100644 gfx/ots/src/gloc.h create mode 100644 gfx/ots/src/graphite.h create mode 100644 gfx/ots/src/gvar.cc create mode 100644 gfx/ots/src/gvar.h create mode 100644 gfx/ots/src/hvar.cc create mode 100644 gfx/ots/src/hvar.h create mode 100644 gfx/ots/src/mvar.cc create mode 100644 gfx/ots/src/mvar.h create mode 100644 gfx/ots/src/sile.cc create mode 100644 gfx/ots/src/sile.h create mode 100644 gfx/ots/src/silf.cc create mode 100644 gfx/ots/src/silf.h create mode 100644 gfx/ots/src/sill.cc create mode 100644 gfx/ots/src/sill.h create mode 100644 gfx/ots/src/stat.cc create mode 100644 gfx/ots/src/stat.h create mode 100644 gfx/ots/src/variations.cc create mode 100644 gfx/ots/src/variations.h create mode 100644 gfx/ots/src/vvar.cc create mode 100644 gfx/ots/src/vvar.h create mode 100644 gfx/ots/tests/cff_charstring_test.cc create mode 100644 gfx/ots/tests/layout_common_table_test.cc (limited to 'gfx/ots') diff --git a/gfx/ots/LICENSE b/gfx/ots/LICENSE index a7531cf7c..d5e3bff5a 100644 --- a/gfx/ots/LICENSE +++ b/gfx/ots/LICENSE @@ -1,27 +1,27 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Copyright (c) 2009-2017 The OTS Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/gfx/ots/README.mozilla b/gfx/ots/README.mozilla index b96826735..0353a99c5 100644 --- a/gfx/ots/README.mozilla +++ b/gfx/ots/README.mozilla @@ -2,10 +2,11 @@ This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/. Our reference repository is https://github.com/khaledhosny/ots/. -Current revision: ba8417620956a920ed1f05a2f666fb6317fb10cb +Current revision: 8bba749d9d5401726a7d7609ab914fdb5e92bfbe (8.0.0) -Upstream files included: LICENSE, src/, include/ +Upstream files included: LICENSE, src/, include/, tests/*.cc Additional files: README.mozilla, src/moz.build Additional patch: ots-visibility.patch (bug 711079). +Additional patch: ots-lz4.patch diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-sanitiser.h index 87d0f3d28..52cbd3aee 100644 --- a/gfx/ots/include/opentype-sanitiser.h +++ b/gfx/ots/include/opentype-sanitiser.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -35,13 +35,17 @@ typedef int int32_t; typedef unsigned int uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; -#define ntohl(x) _byteswap_ulong (x) -#define ntohs(x) _byteswap_ushort (x) -#define htonl(x) _byteswap_ulong (x) -#define htons(x) _byteswap_ushort (x) +#define ots_ntohl(x) _byteswap_ulong (x) +#define ots_ntohs(x) _byteswap_ushort (x) +#define ots_htonl(x) _byteswap_ulong (x) +#define ots_htons(x) _byteswap_ushort (x) #else #include #include +#define ots_ntohl(x) ntohl (x) +#define ots_ntohs(x) ntohs (x) +#define ots_htonl(x) htonl (x) +#define ots_htons(x) htons (x) #endif #include @@ -52,7 +56,7 @@ typedef unsigned __int64 uint64_t; #include #define OTS_TAG(c1,c2,c3,c4) ((uint32_t)((((uint8_t)(c1))<<24)|(((uint8_t)(c2))<<16)|(((uint8_t)(c3))<<8)|((uint8_t)(c4)))) -#define OTS_UNTAG(tag) ((uint8_t)((tag)>>24)), ((uint8_t)((tag)>>16)), ((uint8_t)((tag)>>8)), ((uint8_t)(tag)) +#define OTS_UNTAG(tag) ((char)((tag)>>24)), ((char)((tag)>>16)), ((char)((tag)>>8)), ((char)(tag)) namespace ots { @@ -80,7 +84,7 @@ class OTSStream { const size_t l = std::min(length, static_cast(4) - chksum_offset); uint32_t tmp = 0; std::memcpy(reinterpret_cast(&tmp) + chksum_offset, data, l); - chksum_ += ntohl(tmp); + chksum_ += ots_ntohl(tmp); length -= l; offset += l; } @@ -89,7 +93,7 @@ class OTSStream { uint32_t tmp; std::memcpy(&tmp, reinterpret_cast(data) + offset, sizeof(uint32_t)); - chksum_ += ntohl(tmp); + chksum_ += ots_ntohl(tmp); length -= 4; offset += 4; } @@ -99,7 +103,7 @@ class OTSStream { uint32_t tmp = 0; std::memcpy(&tmp, reinterpret_cast(data) + offset, length); - chksum_ += ntohl(tmp); + chksum_ += ots_ntohl(tmp); } return WriteRaw(data, orig_length); @@ -127,27 +131,27 @@ class OTSStream { } bool WriteU16(uint16_t v) { - v = htons(v); + v = ots_htons(v); return Write(&v, sizeof(v)); } bool WriteS16(int16_t v) { - v = htons(v); + v = ots_htons(v); return Write(&v, sizeof(v)); } bool WriteU24(uint32_t v) { - v = htonl(v); + v = ots_htonl(v); return Write(reinterpret_cast(&v)+1, 3); } bool WriteU32(uint32_t v) { - v = htonl(v); + v = ots_htonl(v); return Write(&v, sizeof(v)); } bool WriteS32(int32_t v) { - v = htonl(v); + v = ots_htonl(v); return Write(&v, sizeof(v)); } @@ -176,7 +180,7 @@ class OTSStream { enum TableAction { TABLE_ACTION_DEFAULT, // Use OTS's default action for that table - TABLE_ACTION_SANITIZE, // Sanitize the table, potentially droping it + TABLE_ACTION_SANITIZE, // Sanitize the table, potentially dropping it TABLE_ACTION_PASSTHRU, // Serialize the table unchanged TABLE_ACTION_DROP // Drop the table }; @@ -186,7 +190,7 @@ class OTS_API OTSContext { OTSContext() {} virtual ~OTSContext() {} - // Process a given OpenType file and write out a sanitised version + // Process a given OpenType file and write out a sanitized version // output: a pointer to an object implementing the OTSStream interface. The // sanitisied output will be written to this. In the even of a failure, // partial output may have been written. diff --git a/gfx/ots/include/ots-memory-stream.h b/gfx/ots/include/ots-memory-stream.h index 579da616f..cd5b089fa 100644 --- a/gfx/ots/include/ots-memory-stream.h +++ b/gfx/ots/include/ots-memory-stream.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/gfx/ots/ots-lz4.patch b/gfx/ots/ots-lz4.patch new file mode 100644 index 000000000..4251e72d6 --- /dev/null +++ b/gfx/ots/ots-lz4.patch @@ -0,0 +1,74 @@ +diff --git a/gfx/ots/src/glat.cc b/gfx/ots/src/glat.cc +--- a/gfx/ots/src/glat.cc ++++ b/gfx/ots/src/glat.cc +@@ -4,9 +4,9 @@ + + #include "glat.h" + + #include "gloc.h" +-#include "lz4.h" ++#include "mozilla/Compression.h" + #include + + namespace ots { + +@@ -212,16 +212,17 @@ bool OpenTypeGLAT_v3::Parse(const uint8_ + return DropGraphite("Decompressed size exceeds 30MB: %gMB", + decompressed_size / (1024.0 * 1024.0)); + } + std::vector decompressed(decompressed_size); +- int ret = LZ4_decompress_safe_partial( ++ size_t outputSize = 0; ++ bool ret = mozilla::Compression::LZ4::decompressPartial( + reinterpret_cast(data + table.offset()), +- reinterpret_cast(decompressed.data()), + table.remaining(), // input buffer size (input size + padding) ++ reinterpret_cast(decompressed.data()), + decompressed.size(), // target output size +- decompressed.size()); // output buffer size +- if (ret < 0 || unsigned(ret) != decompressed.size()) { +- return DropGraphite("Decompression failed with error code %d", ret); ++ &outputSize); // return output size ++ if (!ret || outputSize != decompressed.size()) { ++ return DropGraphite("Decompression failed"); + } + return this->Parse(decompressed.data(), decompressed.size(), true); + } + default: +diff --git a/gfx/ots/src/silf.cc b/gfx/ots/src/silf.cc +--- a/gfx/ots/src/silf.cc ++++ b/gfx/ots/src/silf.cc +@@ -4,9 +4,9 @@ + + #include "silf.h" + + #include "name.h" +-#include "lz4.h" ++#include "mozilla/Compression.h" + #include + + namespace ots { + +@@ -50,16 +50,17 @@ bool OpenTypeSILF::Parse(const uint8_t* + return DropGraphite("Decompressed size exceeds 30MB: %gMB", + decompressed_size / (1024.0 * 1024.0)); + } + std::vector decompressed(decompressed_size); +- int ret = LZ4_decompress_safe_partial( ++ size_t outputSize = 0; ++ bool ret = mozilla::Compression::LZ4::decompressPartial( + reinterpret_cast(data + table.offset()), +- reinterpret_cast(decompressed.data()), + table.remaining(), // input buffer size (input size + padding) ++ reinterpret_cast(decompressed.data()), + decompressed.size(), // target output size +- decompressed.size()); // output buffer size +- if (ret < 0 || unsigned(ret) != decompressed.size()) { +- return DropGraphite("Decompression failed with error code %d", ret); ++ &outputSize); // return output size ++ if (!ret || outputSize != decompressed.size()) { ++ return DropGraphite("Decompression failed"); + } + return this->Parse(decompressed.data(), decompressed.size(), true); + } + default: diff --git a/gfx/ots/ots-visibility.patch b/gfx/ots/ots-visibility.patch index aeac81c4d..c1265472b 100644 --- a/gfx/ots/ots-visibility.patch +++ b/gfx/ots/ots-visibility.patch @@ -1,10 +1,7 @@ diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-sanitiser.h --- a/gfx/ots/include/opentype-sanitiser.h +++ b/gfx/ots/include/opentype-sanitiser.h -@@ -1,15 +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. +@@ -4,8 +4,28 @@ #ifndef OPENTYPE_SANITISER_H_ #define OPENTYPE_SANITISER_H_ @@ -33,15 +30,7 @@ diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-san #include typedef signed char int8_t; typedef unsigned char uint8_t; - typedef short int16_t; - typedef unsigned short uint16_t; - typedef int int32_t; - typedef unsigned int uint32_t; -@@ -187,17 +207,17 @@ class OTSStream { - - enum TableAction { - TABLE_ACTION_DEFAULT, // Use OTS's default action for that table - TABLE_ACTION_SANITIZE, // Sanitize the table, potentially droping it +@@ -164,9 +184,9 @@ enum TableAction { TABLE_ACTION_PASSTHRU, // Serialize the table unchanged TABLE_ACTION_DROP // Drop the table }; @@ -52,7 +41,3 @@ diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-san OTSContext() {} virtual ~OTSContext() {} - // Process a given OpenType file and write out a sanitised version - // output: a pointer to an object implementing the OTSStream interface. The - // sanitisied output will be written to this. In the even of a failure, - // partial output may have been written. diff --git a/gfx/ots/src/avar.cc b/gfx/ots/src/avar.cc new file mode 100644 index 000000000..2a431b1c2 --- /dev/null +++ b/gfx/ots/src/avar.cc @@ -0,0 +1,109 @@ +// Copyright (c) 2018 The OTS 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 "avar.h" + +#include "fvar.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeAVAR +// ----------------------------------------------------------------------------- + +bool OpenTypeAVAR::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + if (!table.ReadU16(&this->majorVersion) || + !table.ReadU16(&this->minorVersion) || + !table.ReadU16(&this->reserved) || + !table.ReadU16(&this->axisCount)) { + return Drop("Failed to read table header"); + } + if (this->majorVersion != 1) { + return Drop("Unknown table version"); + } + if (this->minorVersion > 0) { + // we only know how to serialize version 1.0 + Warning("Downgrading minor version to 0"); + this->minorVersion = 0; + } + if (this->reserved != 0) { + Warning("Expected reserved=0"); + this->reserved = 0; + } + + OpenTypeFVAR* fvar = static_cast( + GetFont()->GetTypedTable(OTS_TAG_FVAR)); + if (!fvar) { + return DropVariations("Required fvar table is missing"); + } + if (axisCount != fvar->AxisCount()) { + return Drop("Axis count mismatch"); + } + + for (size_t i = 0; i < this->axisCount; i++) { + this->axisSegmentMaps.emplace_back(); + uint16_t positionMapCount; + if (!table.ReadU16(&positionMapCount)) { + return Drop("Failed to read position map count"); + } + int foundRequiredMappings = 0; + for (size_t j = 0; j < positionMapCount; j++) { + AxisValueMap map; + if (!table.ReadS16(&map.fromCoordinate) || + !table.ReadS16(&map.toCoordinate)) { + return Drop("Failed to read axis value map"); + } + if (map.fromCoordinate < -0x4000 || + map.fromCoordinate > 0x4000 || + map.toCoordinate < -0x4000 || + map.toCoordinate > 0x4000) { + return Drop("Axis value map coordinate out of range"); + } + if (j > 0) { + if (map.fromCoordinate <= this->axisSegmentMaps[i].back().fromCoordinate || + map.toCoordinate < this->axisSegmentMaps[i].back().toCoordinate) { + return Drop("Axis value map out of order"); + } + } + if ((map.fromCoordinate == -0x4000 && map.toCoordinate == -0x4000) || + (map.fromCoordinate == 0 && map.toCoordinate == 0) || + (map.fromCoordinate == 0x4000 && map.toCoordinate == 0x4000)) { + ++foundRequiredMappings; + } + this->axisSegmentMaps[i].push_back(map); + } + if (positionMapCount > 0 && foundRequiredMappings != 3) { + return Drop("A required mapping (for -1, 0 or 1) is missing"); + } + } + + return true; +} + +bool OpenTypeAVAR::Serialize(OTSStream* out) { + if (!out->WriteU16(this->majorVersion) || + !out->WriteU16(this->minorVersion) || + !out->WriteU16(this->reserved) || + !out->WriteU16(this->axisCount)) { + return Error("Failed to write table"); + } + + for (size_t i = 0; i < this->axisCount; i++) { + const auto& axisValueMap = this->axisSegmentMaps[i]; + if (!out->WriteU16(axisValueMap.size())) { + return Error("Failed to write table"); + } + for (size_t j = 0; j < axisValueMap.size(); j++) { + if (!out->WriteS16(axisValueMap[j].fromCoordinate) || + !out->WriteS16(axisValueMap[j].toCoordinate)) { + return Error("Failed to write table"); + } + } + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/avar.h b/gfx/ots/src/avar.h new file mode 100644 index 000000000..756651c04 --- /dev/null +++ b/gfx/ots/src/avar.h @@ -0,0 +1,42 @@ +// Copyright (c) 2018 The OTS 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_AVAR_H_ +#define OTS_AVAR_H_ + +#include "ots.h" + +#include + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeAVAR Interface +// ----------------------------------------------------------------------------- + +class OpenTypeAVAR : public Table { + public: + explicit OpenTypeAVAR(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + uint16_t majorVersion; + uint16_t minorVersion; + uint16_t reserved; + uint16_t axisCount; + + struct AxisValueMap { + int16_t fromCoordinate; + int16_t toCoordinate; + }; + + std::vector> axisSegmentMaps; +}; + +} // namespace ots + +#endif // OTS_AVAR_H_ diff --git a/gfx/ots/src/cff.cc b/gfx/ots/src/cff.cc index 23b6dadac..b0affd510 100644 --- a/gfx/ots/src/cff.cc +++ b/gfx/ots/src/cff.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright (c) 2012-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,7 +9,8 @@ #include #include "maxp.h" -#include "cff_type2_charstring.h" +#include "cff_charstring.h" +#include "variations.h" // CFF - PostScript font program (Compact Font Format) table // http://www.microsoft.com/typography/otspec/cff.htm @@ -28,6 +29,7 @@ enum DICT_OPERAND_TYPE { enum DICT_DATA_TYPE { DICT_DATA_TOPLEVEL, DICT_DATA_FDARRAY, + DICT_DATA_PRIVATE, }; enum FONT_FORMAT { @@ -39,7 +41,9 @@ enum FONT_FORMAT { // see Appendix. A const size_t kNStdString = 390; -bool ReadOffset(ots::Buffer *table, uint8_t off_size, uint32_t *offset) { +typedef std::pair Operand; + +bool ReadOffset(ots::Buffer &table, uint8_t off_size, uint32_t *offset) { if (off_size > 4) { return OTS_FAILURE(); } @@ -47,7 +51,7 @@ bool ReadOffset(ots::Buffer *table, uint8_t off_size, uint32_t *offset) { uint32_t tmp32 = 0; for (unsigned i = 0; i < off_size; ++i) { uint8_t tmp8 = 0; - if (!table->ReadU8(&tmp8)) { + if (!table.ReadU8(&tmp8)) { return OTS_FAILURE(); } tmp32 <<= 8; @@ -57,39 +61,47 @@ bool ReadOffset(ots::Buffer *table, uint8_t off_size, uint32_t *offset) { return true; } -bool ParseIndex(ots::Buffer *table, ots::CFFIndex *index) { - index->off_size = 0; - index->offsets.clear(); +bool ParseIndex(ots::Buffer &table, ots::CFFIndex &index, bool cff2 = false) { + index.off_size = 0; + index.offsets.clear(); - if (!table->ReadU16(&(index->count))) { - return OTS_FAILURE(); + if (cff2) { + if (!table.ReadU32(&(index.count))) { + return OTS_FAILURE(); + } + } else { + uint16_t count; + if (!table.ReadU16(&count)) { + return OTS_FAILURE(); + } + index.count = count; } - if (index->count == 0) { + + if (index.count == 0) { // An empty INDEX. - index->offset_to_next = table->offset(); + index.offset_to_next = table.offset(); return true; } - if (!table->ReadU8(&(index->off_size))) { + if (!table.ReadU8(&(index.off_size))) { return OTS_FAILURE(); } - if ((index->off_size == 0) || - (index->off_size > 4)) { + if (index.off_size < 1 || index.off_size > 4) { return OTS_FAILURE(); } - const size_t array_size = (index->count + 1) * index->off_size; + 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; + const size_t object_data_offset = table.offset() + array_size; // does not overflow too, since offset() <= 1GB. - if (object_data_offset >= table->length()) { + if (object_data_offset >= table.length()) { return OTS_FAILURE(); } - for (unsigned i = 0; i <= index->count; ++i) { // '<=' is not a typo. + 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)) { + if (!ReadOffset(table, index.off_size, &rel_offset)) { return OTS_FAILURE(); } if (rel_offset < 1) { @@ -99,60 +111,56 @@ bool ParseIndex(ots::Buffer *table, ots::CFFIndex *index) { return OTS_FAILURE(); } - if (rel_offset > table->length()) { + if (rel_offset > table.length()) { return OTS_FAILURE(); } // does not underflow. - if (object_data_offset > table->length() - (rel_offset - 1)) { + if (object_data_offset > table.length() - (rel_offset - 1)) { return OTS_FAILURE(); } - index->offsets.push_back( + index.offsets.push_back( object_data_offset + (rel_offset - 1)); // less than length(), 1GB. } - for (unsigned i = 1; i < index->offsets.size(); ++i) { + 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]) { + if (index.offsets[i] < index.offsets[i - 1]) { return OTS_FAILURE(); } } - index->offset_to_next = index->offsets.back(); + 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. + + const size_t length = index.offsets[1] - index.offsets[0]; + // font names should be no longer than 127 characters. + if (length > 127) { + return OTS_FAILURE(); + } + + table->set_offset(index.offsets[0]); + if (!table->Read(name, length)) { 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)) { + for (size_t i = 0; i < length; ++i) { + // setting the first byte to NUL is allowed. + if (i == 0 && name[i] == 0) continue; + // non-ASCII characters are not recommended (except the first character). + if (name[i] < 33 || name[i] > 126) { 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(); - } + // [, ], ... are not allowed. + if (std::strchr("[](){}<>/% ", name[i])) { + return OTS_FAILURE(); } } @@ -160,8 +168,7 @@ bool ParseNameData( return true; } -bool CheckOffset(const std::pair& operand, - size_t table_length) { +bool CheckOffset(const Operand& operand, size_t table_length) { if (operand.second != DICT_OPERAND_INTEGER) { return OTS_FAILURE(); } @@ -171,8 +178,7 @@ bool CheckOffset(const std::pair& operand, return true; } -bool CheckSid(const std::pair& operand, - size_t sid_max) { +bool CheckSid(const Operand& operand, size_t sid_max) { if (operand.second != DICT_OPERAND_INTEGER) { return OTS_FAILURE(); } @@ -182,30 +188,28 @@ bool CheckSid(const std::pair& operand, return true; } -bool ParseDictDataBcd( - ots::Buffer *table, - std::vector > *operands) { +bool ParseDictDataBcd(ots::Buffer &table, std::vector &operands) { bool read_decimal_point = false; bool read_e = false; uint8_t nibble = 0; size_t count = 0; while (true) { - if (!table->ReadU8(&nibble)) { + 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(0), + operands.push_back(std::make_pair(static_cast(0), DICT_OPERAND_REAL)); return true; } return OTS_FAILURE(); } if ((nibble & 0x0f) == 0x0f) { - operands->push_back(std::make_pair(static_cast(0), + operands.push_back(std::make_pair(static_cast(0), DICT_OPERAND_REAL)); return true; } @@ -242,18 +246,17 @@ bool ParseDictDataBcd( } } -bool ParseDictDataEscapedOperator( - ots::Buffer *table, - std::vector > *operands) { +bool ParseDictDataEscapedOperator(ots::Buffer &table, + std::vector &operands) { uint8_t op = 0; - if (!table->ReadU8(&op)) { + 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)); + operands.push_back(std::make_pair((12U << 8) + op, DICT_OPERATOR)); return true; } @@ -261,9 +264,8 @@ bool ParseDictDataEscapedOperator( return OTS_FAILURE(); } -bool ParseDictDataNumber( - ots::Buffer *table, uint8_t b0, - std::vector > *operands) { +bool ParseDictDataNumber(ots::Buffer &table, uint8_t b0, + std::vector &operands) { uint8_t b1 = 0; uint8_t b2 = 0; uint8_t b3 = 0; @@ -271,22 +273,22 @@ bool ParseDictDataNumber( switch (b0) { case 28: // shortint - if (!table->ReadU8(&b1) || - !table->ReadU8(&b2)) { + if (!table.ReadU8(&b1) || + !table.ReadU8(&b2)) { return OTS_FAILURE(); } - operands->push_back(std::make_pair( + operands.push_back(std::make_pair( static_cast((b1 << 8) + b2), DICT_OPERAND_INTEGER)); return true; case 29: // longint - if (!table->ReadU8(&b1) || - !table->ReadU8(&b2) || - !table->ReadU8(&b3) || - !table->ReadU8(&b4)) { + if (!table.ReadU8(&b1) || + !table.ReadU8(&b2) || + !table.ReadU8(&b3) || + !table.ReadU8(&b4)) { return OTS_FAILURE(); } - operands->push_back(std::make_pair( + operands.push_back(std::make_pair( static_cast((b1 << 24) + (b2 << 16) + (b3 << 8) + b4), DICT_OPERAND_INTEGER)); return true; @@ -302,12 +304,12 @@ bool ParseDictDataNumber( if (b0 >=32 && b0 <=246) { result = b0 - 139; } else if (b0 >=247 && b0 <= 250) { - if (!table->ReadU8(&b1)) { + if (!table.ReadU8(&b1)) { return OTS_FAILURE(); } result = (b0 - 247) * 256 + b1 + 108; } else if (b0 >= 251 && b0 <= 254) { - if (!table->ReadU8(&b1)) { + if (!table.ReadU8(&b1)) { return OTS_FAILURE(); } result = -(b0 - 251) * 256 + b1 - 108; @@ -315,22 +317,21 @@ bool ParseDictDataNumber( return OTS_FAILURE(); } - operands->push_back(std::make_pair(result, DICT_OPERAND_INTEGER)); + operands.push_back(std::make_pair(result, DICT_OPERAND_INTEGER)); return true; } -bool ParseDictDataReadNext( - ots::Buffer *table, - std::vector > *operands) { +bool ParseDictDataReadNext(ots::Buffer &table, + std::vector &operands) { uint8_t op = 0; - if (!table->ReadU8(&op)) { + if (!table.ReadU8(&op)) { return OTS_FAILURE(); } - if (op <= 21) { + if (op <= 24) { if (op == 12) { return ParseDictDataEscapedOperator(table, operands); } - operands->push_back(std::make_pair( + operands.push_back(std::make_pair( static_cast(op), DICT_OPERATOR)); return true; } else if (op <= 27 || op == 31 || op == 255) { @@ -341,12 +342,69 @@ bool ParseDictDataReadNext( return ParseDictDataNumber(table, op, operands); } +bool OperandsOverflow(std::vector& operands, bool cff2) { + // An operator may be preceded by up to a maximum of 48 operands in CFF1 and + // 513 operands in CFF2. + if ((cff2 && operands.size() > ots::kMaxCFF2ArgumentStack) || + (!cff2 && operands.size() > ots::kMaxCFF1ArgumentStack)) { + return true; + } + return false; +} + +bool ParseDictDataReadOperands(ots::Buffer& dict, + std::vector& operands, + bool cff2) { + if (!ParseDictDataReadNext(dict, operands)) { + return OTS_FAILURE(); + } + if (operands.empty()) { + return OTS_FAILURE(); + } + if (OperandsOverflow(operands, cff2)) { + return OTS_FAILURE(); + } + return true; +} + +bool ValidCFF2DictOp(uint32_t op, DICT_DATA_TYPE type) { + if (type == DICT_DATA_TOPLEVEL) { + switch (op) { + case (12U << 8) + 7: // FontMatrix + case 17: // CharStrings + case (12U << 8) + 36: // FDArray + case (12U << 8) + 37: // FDSelect + case 24: // vstore + return true; + default: + return false; + } + } else if (type == DICT_DATA_FDARRAY) { + if (op == 18) // Private DICT + return true; + } else if (type == DICT_DATA_PRIVATE) { + switch (op) { + case (12U << 8) + 14: // ForceBold + case (12U << 8) + 19: // initialRandomSeed + case 20: // defaultWidthX + case 21: // nominalWidthX + return false; + default: + return true; + } + } + + return false; +} + bool ParsePrivateDictData( - const uint8_t *data, - size_t table_length, size_t offset, size_t dict_length, + ots::Buffer &table, size_t offset, size_t dict_length, DICT_DATA_TYPE type, ots::OpenTypeCFF *out_cff) { - ots::Buffer table(data + offset, dict_length); - std::vector > operands; + ots::Buffer dict(table.buffer() + offset, dict_length); + std::vector operands; + bool cff2 = (out_cff->major == 2); + bool blend_seen = false; + uint32_t vsindex = 0; // 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 @@ -355,15 +413,8 @@ bool ParsePrivateDictData( 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. + while (dict.offset() < dict.length()) { + if (!ParseDictDataReadOperands(dict, operands, cff2)) { return OTS_FAILURE(); } if (operands.back().second != DICT_OPERATOR) { @@ -374,13 +425,18 @@ bool ParsePrivateDictData( const uint32_t op = operands.back().first; operands.pop_back(); + if (cff2 && !ValidCFF2DictOp(op, DICT_DATA_PRIVATE)) { + return OTS_FAILURE(); + } + + bool clear_operands = true; switch (op) { // hints case 6: // BlueValues case 7: // OtherBlues case 8: // FamilyBlues case 9: // FamilyOtherBlues - if (operands.empty() || (operands.size() % 2) != 0) { + if ((operands.size() % 2) != 0) { return OTS_FAILURE(); } break; @@ -420,12 +476,11 @@ bool ParsePrivateDictData( if (operands.back().first >= 1024 * 1024 * 1024) { return OTS_FAILURE(); } - if (operands.back().first + offset >= table_length) { + 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); + 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()) { @@ -439,7 +494,7 @@ bool ParsePrivateDictData( local_subrs_index = new ots::CFFIndex; out_cff->local_subrs = local_subrs_index; } - if (!ParseIndex(&cff_table, local_subrs_index)) { + if (!ParseIndex(table, *local_subrs_index, cff2)) { return OTS_FAILURE(); } break; @@ -458,42 +513,125 @@ bool ParsePrivateDictData( } break; + case 22: { // vsindex + if (!cff2) { + return OTS_FAILURE(); + } + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (blend_seen) { + return OTS_FAILURE(); + } + vsindex = operands.back().first; + if (vsindex >= out_cff->region_index_count.size()) { + return OTS_FAILURE(); + } + break; + } + + case 23: { // blend + if (!cff2) { + return OTS_FAILURE(); + } + if (operands.size() < 1) { + return OTS_FAILURE(); + } + if (vsindex >= out_cff->region_index_count.size()) { + return OTS_FAILURE(); + } + uint16_t k = out_cff->region_index_count.at(vsindex); + uint16_t n = operands.back().first; + if (operands.size() < n * (k + 1) + 1) { + return OTS_FAILURE(); + } + size_t operands_size = operands.size(); + // Keep the 1st n operands on the stack for the next operator to use + // and pop the rest. There can be multiple consecutive blend operator, + // so this makes sure the operands of all of them are kept on the + // stack. + while (operands.size() > operands_size - ((n * k) + 1)) + operands.pop_back(); + clear_operands = false; + blend_seen = true; + break; + } + default: return OTS_FAILURE(); } - operands.clear(); + if (clear_operands) { + operands.clear(); + } + } + + return true; +} + +bool ParseVariationStore(ots::OpenTypeCFF& out_cff, ots::Buffer& table) { + uint16_t length; + + if (!table.ReadU16(&length)) { + return OTS_FAILURE(); + } + + // Empty VariationStore is allowed. + if (!length) { + return true; + } + + if (length > table.remaining()) { + return OTS_FAILURE(); + } + + if (!ParseItemVariationStore(out_cff.GetFont(), + table.buffer() + table.offset(), length, + &(out_cff.region_index_count))) { + return OTS_FAILURE(); } 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, +bool ParseDictData(ots::Buffer& table, ots::Buffer& dict, + uint16_t glyphs, size_t sid_max, DICT_DATA_TYPE type, + ots::OpenTypeCFF *out_cff); + +bool ParseDictData(ots::Buffer& table, 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 > operands; + ots::Buffer dict(table.buffer() + index.offsets[i - 1], dict_length); - FONT_FORMAT font_format = FORMAT_UNKNOWN; - bool have_ros = false; - uint16_t charstring_glyphs = 0; - size_t charset_offset = 0; + if (!ParseDictData(table, dict, glyphs, sid_max, type, out_cff)) { + return OTS_FAILURE(); + } + } + return true; +} - 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. +bool ParseDictData(ots::Buffer& table, ots::Buffer& dict, + uint16_t glyphs, size_t sid_max, DICT_DATA_TYPE type, + ots::OpenTypeCFF *out_cff) { + bool cff2 = (out_cff->major == 2); + std::vector operands; + + FONT_FORMAT font_format = FORMAT_UNKNOWN; + bool have_ros = false; + bool have_charstrings = false; + bool have_vstore = false; + size_t charset_offset = 0; + + if (cff2) { + // Parse VariationStore first, since it might be referenced in other places + // (e.g. FDArray) that might be parsed after it. + size_t dict_offset = dict.offset(); + while (dict.offset() < dict.length()) { + if (!ParseDictDataReadOperands(dict, operands, cff2)) { return OTS_FAILURE(); } if (operands.back().second != DICT_OPERATOR) continue; @@ -502,399 +640,503 @@ bool ParseDictData(const uint8_t *data, size_t table_length, 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; + if (op == 24) { // vstore + 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 "VariationStore Data Contents" + table.set_offset(operands.back().first); + if (!ParseVariationStore(*out_cff, table)) { + return OTS_FAILURE(); + } + break; + } + operands.clear(); + } + operands.clear(); + dict.set_offset(dict_offset); + } - // 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; + while (dict.offset() < dict.length()) { + if (!ParseDictDataReadOperands(dict, operands, cff2)) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERATOR) continue; - // 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; + // got operator + const uint32_t op = operands.back().first; + operands.pop_back(); - // 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; + if (cff2 && !ValidCFF2DictOp(op, type)) { + return OTS_FAILURE(); + } - 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(); - } + 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; - // 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; + // 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; - 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; + // 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; - 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(); - } + // 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; - // 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; + // 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 (12U << 8) + 37: { // FDSelect - if (type != DICT_DATA_TOPLEVEL) { - return OTS_FAILURE(); + 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(); + } + + table.set_offset(operands.back().first); + uint8_t format = 0; + if (!table.ReadU8(&format)) { + return OTS_FAILURE(); + } + if (format & 0x80) { + // supplemental encoding is not supported at the moment. + return OTS_FAILURE(); + } + // TODO(yusukes): support & parse supplemental encoding tables. + break; + } + + case 17: { // CharStrings + if (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" + table.set_offset(operands.back().first); + ots::CFFIndex *charstring_index = out_cff->charstrings_index; + if (!ParseIndex(table, *charstring_index, cff2)) { + return OTS_FAILURE(); + } + if (charstring_index->count < 2) { + return OTS_FAILURE(); + } + if (have_charstrings) { + return OTS_FAILURE(); // multiple charstring tables? + } + have_charstrings = true; + if (charstring_index->count != glyphs) { + return OTS_FAILURE(); // CFF and maxp have different number of glyphs? + } + break; + } + + case 24: { // vstore + if (!cff2) { + return OTS_FAILURE(); + } + if (have_vstore) { + return OTS_FAILURE(); // multiple vstore tables? + } + have_vstore = true; + // parsed above. + 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 Font DICT INDEX. + table.set_offset(operands.back().first); + ots::CFFIndex sub_dict_index; + if (!ParseIndex(table, sub_dict_index, cff2)) { + return OTS_FAILURE(); + } + if (!ParseDictData(table, 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 + table.set_offset(operands.back().first); + uint8_t format = 0; + if (!table.ReadU8(&format)) { + return OTS_FAILURE(); + } + if (format == 0) { + for (uint16_t j = 0; j < glyphs; ++j) { + uint8_t fd_index = 0; + if (!table.ReadU8(&fd_index)) { + return OTS_FAILURE(); + } + (out_cff->fd_select)[j] = fd_index; } - if (operands.size() != 1) { + } else if (format == 3) { + uint16_t n_ranges = 0; + if (!table.ReadU16(&n_ranges)) { return OTS_FAILURE(); } - if (!CheckOffset(operands.back(), table_length)) { + if (n_ranges == 0) { 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)) { + uint16_t last_gid = 0; + uint8_t fd_index = 0; + for (unsigned j = 0; j < n_ranges; ++j) { + uint16_t first = 0; // GID + if (!table.ReadU16(&first)) { return OTS_FAILURE(); } - if (n_ranges == 0) { + + // Sanity checks. + if ((j == 0) && (first != 0)) { return OTS_FAILURE(); } + if ((j != 0) && (last_gid >= first)) { + return OTS_FAILURE(); // not increasing order. + } + if (first >= glyphs) { + return OTS_FAILURE(); // invalid gid. + } - 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(); - } + // Copy the mapping to |out_cff->fd_select|. + if (j != 0) { + for (auto 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) { + + if (!table.ReadU8(&fd_index)) { 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(); + last_gid = first; } - if (operands.back().second != DICT_OPERAND_INTEGER) { + uint16_t sentinel = 0; + if (!table.ReadU16(&sentinel)) { return OTS_FAILURE(); } - const uint32_t private_offset = operands.back().first; - operands.pop_back(); - if (operands.back().second != DICT_OPERAND_INTEGER) { + if (last_gid >= sentinel) { return OTS_FAILURE(); } - const uint32_t private_length = operands.back().first; - if (private_offset > table_length) { - return OTS_FAILURE(); + if (sentinel > glyphs) { + return OTS_FAILURE(); // invalid gid. } - if (private_length >= table_length) { - return OTS_FAILURE(); + for (auto k = last_gid; k < sentinel; ++k) { + if (!out_cff->fd_select.insert( + std::make_pair(k, fd_index)).second) { + return OTS_FAILURE(); + } } - if (private_length + private_offset > table_length) { + } else if (cff2 && format == 4) { + uint32_t n_ranges = 0; + if (!table.ReadU32(&n_ranges)) { return OTS_FAILURE(); } - // parse "15. Private DICT Data" - if (!ParsePrivateDictData(data, table_length, - private_offset, private_length, - type, out_cff)) { + if (n_ranges == 0) { return OTS_FAILURE(); } - break; - } - // ROS - case (12U << 8) + 30: - if (font_format != FORMAT_UNKNOWN) { - return OTS_FAILURE(); + uint32_t last_gid = 0; + uint16_t fd_index = 0; + for (unsigned j = 0; j < n_ranges; ++j) { + uint32_t first = 0; // GID + if (!table.ReadU32(&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. + } + if (first >= glyphs) { + return OTS_FAILURE(); // invalid gid. + } + + // Copy the mapping to |out_cff->fd_select|. + if (j != 0) { + for (auto k = last_gid; k < first; ++k) { + if (!out_cff->fd_select.insert( + std::make_pair(k, fd_index)).second) { + return OTS_FAILURE(); + } + } + } + + if (!table.ReadU16(&fd_index)) { + return OTS_FAILURE(); + } + last_gid = first; } - font_format = FORMAT_CID_KEYED; - if (operands.size() != 3) { + uint32_t sentinel = 0; + if (!table.ReadU32(&sentinel)) { return OTS_FAILURE(); } - // check SIDs - operands.pop_back(); // ignore the first number. - if (!CheckSid(operands.back(), sid_max)) { + if (last_gid >= sentinel) { return OTS_FAILURE(); } - operands.pop_back(); - if (!CheckSid(operands.back(), sid_max)) { - return OTS_FAILURE(); + if (sentinel > glyphs) { + return OTS_FAILURE(); // invalid gid. } - if (have_ros) { - return OTS_FAILURE(); // multiple ROS tables? + for (auto k = last_gid; k < sentinel; ++k) { + if (!out_cff->fd_select.insert( + std::make_pair(k, fd_index)).second) { + return OTS_FAILURE(); + } } - have_ros = true; - break; - - default: + } else { + // unknown format return OTS_FAILURE(); + } + break; } - operands.clear(); - if (font_format == FORMAT_UNKNOWN) { - font_format = FORMAT_OTHER; + // 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(table, private_offset, private_length, + type, out_cff)) { + return OTS_FAILURE(); + } + break; } - } - // 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)) { + // 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(); - } - 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. + } + operands.clear(); + + if (font_format == FORMAT_UNKNOWN) { + font_format = FORMAT_OTHER; + } + } + + // parse "13. Charsets" + if (charset_offset) { + table.set_offset(charset_offset); + uint8_t format = 0; + if (!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 (!table.ReadU16(&sid)) { + return OTS_FAILURE(); } - break; + 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)) { + case 1: + case 2: { + uint32_t total = 1; // .notdef is omitted. + while (total < glyphs) { + uint16_t sid = 0; + if (!table.ReadU16(&sid)) { + return OTS_FAILURE(); + } + if (!have_ros && (sid > sid_max)) { + return OTS_FAILURE(); + } + // TODO(yusukes): check CIDs when have_ros is true. + + if (format == 1) { + uint8_t left = 0; + if (!table.ReadU8(&left)) { return OTS_FAILURE(); } - if (!have_ros && (sid > sid_max)) { + total += (left + 1); + } else { + uint16_t left = 0; + if (!table.ReadU16(&left)) { 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); - } + total += (left + 1); } - break; } - - default: - return OTS_FAILURE(); + break; } + + default: + return OTS_FAILURE(); } } return true; @@ -904,147 +1146,218 @@ bool ParseDictData(const uint8_t *data, size_t table_length, namespace ots { -bool ots_cff_parse(Font *font, const uint8_t *data, size_t length) { +bool OpenTypeCFF::ValidateFDSelect(uint16_t num_glyphs) { + for (const auto& fd_select : this->fd_select) { + if (fd_select.first >= num_glyphs) { + return Error("Invalid glyph index in FDSelect: %d >= %d\n", + fd_select.first, num_glyphs); + } + if (fd_select.second >= this->font_dict_length) { + return Error("Invalid FD index: %d >= %d\n", + fd_select.second, this->font_dict_length); + } + } + return true; +} + +bool OpenTypeCFF::Parse(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; + Font *font = GetFont(); + + this->m_data = data; + this->m_length = length; // parse "6. Header" in the Adobe Compact Font Format Specification uint8_t major = 0; uint8_t minor = 0; uint8_t hdr_size = 0; uint8_t off_size = 0; - if (!table.ReadU8(&major)) { - return OTS_FAILURE(); + if (!table.ReadU8(&major) || + !table.ReadU8(&minor) || + !table.ReadU8(&hdr_size) || + !table.ReadU8(&off_size)) { + return Error("Failed to read table header"); } - 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 (off_size < 1 || off_size > 4) { + return Error("Bad offSize: %d", off_size); } - if ((major != 1) || - (minor != 0) || - (hdr_size != 4)) { - return OTS_FAILURE(); + if (major != 1 || minor != 0) { + return Error("Unsupported table version: %d.%d", major, minor); } - if (hdr_size >= length) { - return OTS_FAILURE(); + + this->major = major; + + if (hdr_size != 4 || hdr_size >= length) { + return Error("Bad hdrSize: %d", hdr_size); } // parse "7. Name INDEX" table.set_offset(hdr_size); CFFIndex name_index; - if (!ParseIndex(&table, &name_index)) { - return OTS_FAILURE(); + if (!ParseIndex(table, name_index)) { + return Error("Failed to parse Name INDEX"); } - if (!ParseNameData(&table, name_index, &(font->cff->name))) { - return OTS_FAILURE(); + if (name_index.count != 1 || name_index.offsets.size() != 2) { + return Error("Name INDEX must contain only one entry, not %d", + name_index.count); + } + if (!ParseNameData(&table, name_index, &(this->name))) { + return Error("Failed to parse Name INDEX data"); } // 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 (!ParseIndex(table, top_dict_index)) { + return Error("Failed to parse Top DICT INDEX"); } - if (name_index.count != top_dict_index.count) { - return OTS_FAILURE(); + if (top_dict_index.count != 1) { + return Error("Top DICT INDEX must contain only one entry, not %d", + top_dict_index.count); } // 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 (!ParseIndex(table, string_index)) { + return Error("Failed to parse String INDEX"); } if (string_index.count >= 65000 - kNStdString) { - return OTS_FAILURE(); + return Error("Too many entries in String INDEX: %d", string_index.count); } - const uint16_t num_glyphs = font->maxp->num_glyphs; + OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + const uint16_t num_glyphs = 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, + this->charstrings_index = new ots::CFFIndex; + if (!ParseDictData(table, top_dict_index, num_glyphs, sid_max, - DICT_DATA_TOPLEVEL, font->cff)) { - return OTS_FAILURE(); + DICT_DATA_TOPLEVEL, this)) { + return Error("Failed to parse Top DICT Data"); } // 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(); + if (!ParseIndex(table, global_subrs_index)) { + return Error("Failed to parse Global Subrs INDEX"); } - // Check if all fd_index in FDSelect are valid. - std::map::const_iterator iter; - std::map::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 fd and glyph indices in FDSelect are valid. + if (!ValidateFDSelect(num_glyphs)) { + return Error("Failed to validate FDSelect"); } // 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); - } + if (!ValidateCFFCharStrings(*this, global_subrs_index, &table)) { + return Error("Failed validating CharStrings INDEX"); } return true; } -bool ots_cff_should_serialise(Font *font) { - return font->cff != NULL; +bool OpenTypeCFF::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write table"); + } + return true; } -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(); +OpenTypeCFF::~OpenTypeCFF() { + for (size_t i = 0; i < this->local_subrs_per_font.size(); ++i) { + delete (this->local_subrs_per_font)[i]; } - return true; + delete this->charstrings_index; + delete this->local_subrs; } -void ots_cff_reuse(Font *font, Font *other) { - font->cff = other->cff; - font->cff_reused = true; +bool OpenTypeCFF2::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + Font *font = GetFont(); + + this->m_data = data; + this->m_length = length; + + // parse "6. Header" + uint8_t major = 0; + uint8_t minor = 0; + uint8_t hdr_size = 0; + uint16_t top_dict_size = 0; + if (!table.ReadU8(&major) || + !table.ReadU8(&minor) || + !table.ReadU8(&hdr_size) || + !table.ReadU16(&top_dict_size)) { + return Error("Failed to read table header"); + } + + if (major != 2 || minor != 0) { + return Error("Unsupported table version: %d.%d", major, minor); + } + + this->major = major; + + if (hdr_size >= length) { + return Error("Bad hdrSize: %d", hdr_size); + } + + if (top_dict_size == 0 || hdr_size + top_dict_size > length) { + return Error("Bad topDictLength: %d", top_dict_size); + } + + OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; + const size_t sid_max = kNStdString; + + // parse "7. Top DICT Data" + ots::Buffer top_dict(data + hdr_size, top_dict_size); + table.set_offset(hdr_size); + this->charstrings_index = new ots::CFFIndex; + if (!ParseDictData(table, top_dict, + num_glyphs, sid_max, + DICT_DATA_TOPLEVEL, this)) { + return Error("Failed to parse Top DICT Data"); + } + + // parse "9. Global Subrs INDEX" + table.set_offset(hdr_size + top_dict_size); + CFFIndex global_subrs_index; + if (!ParseIndex(table, global_subrs_index, true)) { + return Error("Failed to parse Global Subrs INDEX"); + } + + // Check if all fd and glyph indices in FDSelect are valid. + if (!ValidateFDSelect(num_glyphs)) { + return Error("Failed to validate FDSelect"); + } + + // Check if all charstrings (font hinting code for each glyph) are valid. + if (!ValidateCFFCharStrings(*this, global_subrs_index, &table)) { + return Error("Failed validating CharStrings INDEX"); + } + + return 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; +bool OpenTypeCFF2::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write table"); } + return true; } } // namespace ots diff --git a/gfx/ots/src/cff.h b/gfx/ots/src/cff.h index 5584acc62..cfce9ab91 100644 --- a/gfx/ots/src/cff.h +++ b/gfx/ots/src/cff.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -11,34 +11,81 @@ #include #include +#undef major // glibc defines major! + namespace ots { struct CFFIndex { CFFIndex() : count(0), off_size(0), offset_to_next(0) {} - uint16_t count; + uint32_t count; uint8_t off_size; std::vector offsets; uint32_t offset_to_next; }; -struct OpenTypeCFF { - const uint8_t *data; - size_t length; +typedef std::map CFFFDSelect; + +class OpenTypeCFF : public Table { + public: + explicit OpenTypeCFF(Font *font, uint32_t tag) + : Table(font, tag, tag), + major(0), + font_dict_length(0), + charstrings_index(NULL), + local_subrs(NULL), + m_data(NULL), + m_length(0) { + } + + ~OpenTypeCFF(); + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + // Major version number. + uint8_t major; + // 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 fd_select; + CFFFDSelect fd_select; // A list of char strings. - std::vector char_strings_array; + CFFIndex* charstrings_index; // A list of Local Subrs associated with FDArrays. Can be empty. std::vector local_subrs_per_font; // A Local Subrs associated with Top DICT. Can be NULL. CFFIndex *local_subrs; + + // CFF2 VariationStore regionIndexCount. + std::vector region_index_count; + + protected: + bool ValidateFDSelect(uint16_t num_glyphs); + + private: + const uint8_t *m_data; + size_t m_length; +}; + +class OpenTypeCFF2 : public OpenTypeCFF { + public: + explicit OpenTypeCFF2(Font *font, uint32_t tag) + : OpenTypeCFF(font, tag), + m_data(NULL), + m_length(0) { + } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + private: + const uint8_t *m_data; + size_t m_length; }; } // namespace ots diff --git a/gfx/ots/src/cff_charstring.cc b/gfx/ots/src/cff_charstring.cc new file mode 100644 index 000000000..23c17d183 --- /dev/null +++ b/gfx/ots/src/cff_charstring.cc @@ -0,0 +1,1018 @@ +// Copyright (c) 2010-2017 The OTS 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_charstring.h" + +#include +#include +#include +#include +#include +#include + +#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 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 ExecuteCharString(ots::OpenTypeCFF& cff, + 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 *argument_stack, + bool *out_found_endchar, + bool *out_found_width, + size_t *in_out_num_stems, + bool cff2); + +bool ArgumentStackOverflows(std::stack *argument_stack, bool cff2) { + if ((cff2 && argument_stack->size() > ots::kMaxCFF2ArgumentStack) || + (!cff2 && argument_stack->size() > ots::kMaxCFF1ArgumentStack)) { + return true; + } + return false; +} + +#ifdef DUMP_T2CHARSTRING +// Converts |op| to a string and returns it. +const char *CharStringOperatorToString(ots::CharStringOperator 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::kVSIndex: + return "vsindex"; + case ots::kBlend: + return "blend"; + 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 ReadNextNumberFromCharString(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 + // ExecuteCharStringOperator(). + *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(v) - 139; + } else if (v <= 250) { + uint8_t w = 0; + if (!char_string->ReadU8(&w)) { + return OTS_FAILURE(); + } + *out_number = ((static_cast(v) - 247) * 256) + + static_cast(w) + 108; + } else if (v <= 254) { + uint8_t w = 0; + if (!char_string->ReadU8(&w)) { + return OTS_FAILURE(); + } + *out_number = -((static_cast(v) - 251) * 256) - + static_cast(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; +} + +bool ValidCFF2Operator(int32_t op) { + switch (op) { + case ots::kReturn: + case ots::kEndChar: + case ots::kAbs: + case ots::kAdd: + case ots::kSub: + case ots::kDiv: + case ots::kNeg: + case ots::kRandom: + case ots::kMul: + case ots::kSqrt: + case ots::kDrop: + case ots::kExch: + case ots::kIndex: + case ots::kRoll: + case ots::kDup: + case ots::kPut: + case ots::kGet: + case ots::kDotSection: + case ots::kAnd: + case ots::kOr: + case ots::kNot: + case ots::kEq: + case ots::kIfElse: + return false; + } + + 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 ExecuteCharString() function. The arguments other than |op| and +// |argument_stack| are passed for that reason. +bool ExecuteCharStringOperator(ots::OpenTypeCFF& cff, + 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 *argument_stack, + bool *out_found_endchar, + bool *in_out_found_width, + size_t *in_out_num_stems, + bool *in_out_have_blend, + bool *in_out_have_visindex, + int32_t *in_out_vsindex, + bool cff2) { + ots::Font* font = cff.GetFont(); + const size_t stack_size = argument_stack->size(); + + if (cff2 && !ValidCFF2Operator(op)) { + return OTS_FAILURE(); + } + + 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(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 ExecuteCharString(cff, + 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, + cff2); + } + + case ots::kReturn: + return true; + + case ots::kEndChar: + *out_found_endchar = true; + *in_out_found_width = true; // just in case. + return true; + + case ots::kVSIndex: { + if (!cff2) { + return OTS_FAILURE(); + } + if (stack_size != 1) { + return OTS_FAILURE(); + } + if (*in_out_have_blend || *in_out_have_visindex) { + return OTS_FAILURE(); + } + if (argument_stack->top() >= cff.region_index_count.size()) { + return OTS_FAILURE(); + } + *in_out_have_visindex = true; + *in_out_vsindex = argument_stack->top(); + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + } + + case ots::kBlend: { + if (!cff2) { + return OTS_FAILURE(); + } + if (stack_size < 1) { + return OTS_FAILURE(); + } + if (*in_out_vsindex >= cff.region_index_count.size()) { + return OTS_FAILURE(); + } + uint16_t k = cff.region_index_count.at(*in_out_vsindex); + uint16_t n = argument_stack->top(); + if (stack_size < n * (k + 1) + 1) { + return OTS_FAILURE(); + } + + // Keep the 1st n operands on the stack for the next operator to use and + // pop the rest. There can be multiple consecutive blend operator, so this + // makes sure the operands of all of them are kept on the stack. + while (argument_stack->size() > stack_size - ((n * k) + 1)) + argument_stack->pop(); + *in_out_have_blend = true; + 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 (ArgumentStackOverflows(argument_stack, cff2)) { + 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 ExecuteCharString(ots::OpenTypeCFF& cff, + 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 *argument_stack, + bool *out_found_endchar, + bool *in_out_found_width, + size_t *in_out_num_stems, + bool cff2) { + if (call_depth > kMaxSubrNesting) { + return OTS_FAILURE(); + } + *out_found_endchar = false; + + bool in_out_have_blend = false, in_out_have_visindex = false; + int32_t in_out_vsindex = 0; + const size_t length = char_string->length(); + while (char_string->offset() < length) { + int32_t operator_or_operand = 0; + bool is_operator = false; + if (!ReadNextNumberFromCharString(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", + CharStringOperatorToString( + ots::CharStringOperator(operator_or_operand)) + ); + } +#endif + + if (!is_operator) { + argument_stack->push(operator_or_operand); + if (ArgumentStackOverflows(argument_stack, cff2)) { + return OTS_FAILURE(); + } + continue; + } + + // An operator is found. Execute it. + if (!ExecuteCharStringOperator(cff, + 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, + &in_out_have_blend, + &in_out_have_visindex, + &in_out_vsindex, + cff2)) { + return OTS_FAILURE(); + } + if (*out_found_endchar) { + return true; + } + if (operator_or_operand == ots::kReturn) { + return true; + } + } + + // No endchar operator is found. + if (cff2) + return true; + 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 ots::OpenTypeCFF& cff, + uint16_t glyph_index, // 0-origin + const ots::CFFIndex **out_local_subrs_to_use) { + bool cff2 = (cff.major == 2); + *out_local_subrs_to_use = NULL; + + // First, find local subrs from |local_subrs_per_font|. + if ((cff.fd_select.size() > 0) && + (!cff.local_subrs_per_font.empty())) { + // Look up FDArray index for the glyph. + const auto& iter = cff.fd_select.find(glyph_index); + if (iter == cff.fd_select.end()) { + return OTS_FAILURE(); + } + const auto fd_index = iter->second; + if (fd_index >= cff.local_subrs_per_font.size()) { + return OTS_FAILURE(); + } + *out_local_subrs_to_use = cff.local_subrs_per_font.at(fd_index); + } else if (cff.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 = cff.local_subrs; + } else if (cff2 && cff.local_subrs_per_font.size() == 1) { + *out_local_subrs_to_use = cff.local_subrs_per_font.at(0); + } else { + // Just return NULL. + *out_local_subrs_to_use = NULL; + } + + return true; +} + +} // namespace + +namespace ots { + +bool ValidateCFFCharStrings( + ots::OpenTypeCFF& cff, + const CFFIndex& global_subrs_index, + Buffer* cff_table) { + const CFFIndex& char_strings_index = *(cff.charstrings_index); + if (char_strings_index.offsets.size() == 0) { + return OTS_FAILURE(); // no charstring. + } + + bool cff2 = (cff.major == 2); + // 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(cff, + 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 argument_stack; + bool found_endchar = false; + // CFF2 CharString has no value for width, so we start with true here to + // error out if width is found. + bool found_width = cff2; + size_t num_stems = 0; + if (!ExecuteCharString(cff, + 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, + cff2)) { + return OTS_FAILURE(); + } + if (!cff2 && !found_endchar) { + return OTS_FAILURE(); + } + } + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/cff_charstring.h b/gfx/ots/src/cff_charstring.h new file mode 100644 index 000000000..5a2fa9ff2 --- /dev/null +++ b/gfx/ots/src/cff_charstring.h @@ -0,0 +1,103 @@ +// Copyright (c) 2010-2017 The OTS 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 +#include + +namespace ots { + +const size_t kMaxCFF1ArgumentStack = 48; +const size_t kMaxCFF2ArgumentStack = 513; + +// 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 ValidateCFFCharStrings( + OpenTypeCFF& cff, + const CFFIndex &global_subrs_index, + Buffer *cff_table); + +// The list of Operators. See Appendix. A in Adobe Technical Note #5177. +// and https://docs.microsoft.com/en-us/typography/opentype/spec/cff2charstr +enum CharStringOperator { + kHStem = 1, + kVStem = 3, + kVMoveTo = 4, + kRLineTo = 5, + kHLineTo = 6, + kVLineTo = 7, + kRRCurveTo = 8, + kCallSubr = 10, + kReturn = 11, + kEndChar = 14, + kVSIndex = 15, + kBlend = 16, + 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/cff_type2_charstring.cc b/gfx/ots/src/cff_type2_charstring.cc deleted file mode 100644 index 8c5544b2e..000000000 --- a/gfx/ots/src/cff_type2_charstring.cc +++ /dev/null @@ -1,912 +0,0 @@ -// 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 -#include -#include -#include -#include -#include - -#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 *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(v) - 139; - } else if (v <= 250) { - uint8_t w = 0; - if (!char_string->ReadU8(&w)) { - return OTS_FAILURE(); - } - *out_number = ((static_cast(v) - 247) * 256) + - static_cast(w) + 108; - } else if (v <= 254) { - uint8_t w = 0; - if (!char_string->ReadU8(&w)) { - return OTS_FAILURE(); - } - *out_number = -((static_cast(v) - 251) * 256) - - static_cast(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 *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(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 *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 &fd_select, - const std::vector &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::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 &fd_select, - const std::vector &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 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 deleted file mode 100644 index be44bc72c..000000000 --- a/gfx/ots/src/cff_type2_charstring.h +++ /dev/null @@ -1,101 +0,0 @@ -// 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 -#include - -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 &fd_select, - const std::vector &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 index 325f8e0d1..72c2a20fc 100644 --- a/gfx/ots/src/cmap.cc +++ b/gfx/ots/src/cmap.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -15,8 +15,6 @@ // cmap - Character To Glyph Index Mapping Table // http://www.microsoft.com/typography/otspec/cmap.htm -#define TABLE_NAME "cmap" - namespace { struct CMAPSubtableHeader { @@ -56,8 +54,12 @@ const uint32_t kIVSStart = 0xE0100; const uint32_t kIVSEnd = 0xE01EF; const uint32_t kUVSUpperLimit = 0xFFFFFF; +} // namespace + +namespace ots { + // Parses Format 4 tables -bool ParseFormat4(ots::Font *font, int platform, int encoding, +bool OpenTypeCMAP::ParseFormat4(int platform, int encoding, const uint8_t *data, size_t length, uint16_t num_glyphs) { ots::Buffer subtable(data, length); @@ -65,20 +67,22 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding, // 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"); + OpenTypeOS2 *os2 = static_cast( + GetFont()->GetTypedTable(OTS_TAG_OS2)); + if (!os2) { + return Error("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"); + return Error("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"); + return Error("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); + return Error("Languages should be 0 (%d)", language); } uint16_t segcountx2, search_range, entry_selector, range_shift; @@ -87,16 +91,16 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding, !subtable.ReadU16(&search_range) || !subtable.ReadU16(&entry_selector) || !subtable.ReadU16(&range_shift)) { - return OTS_FAILURE_MSG("Failed to read subcmap structure"); + return Error("Failed to read subcmap structure"); } if (segcountx2 & 1 || search_range & 1) { - return OTS_FAILURE_MSG("Bad subcmap structure"); + return Error("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); + return Error("Segcount < 1 (%d)", segcount); } // log2segcount is the maximal x s.t. 2^x < segcount @@ -107,48 +111,48 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding, 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); + return Error("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); + return Error("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); + return Error("unexpected range shift (%d != %d)", range_shift, expected_range_shift); } std::vector 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); + return Error("Failed to read segment %d", i); } } uint16_t padding; if (!subtable.ReadU16(&padding)) { - return OTS_FAILURE_MSG("Failed to read cmap subtable segment padding"); + return Error("Failed to read cmap subtable segment padding"); } if (padding) { - return OTS_FAILURE_MSG("Non zero cmap subtable segment padding (%d)", padding); + return Error("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); + return Error("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); + return Error("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); + return Error("Failed to read segment range offset %d", i); } if (ranges[i].id_range_offset & 1) { @@ -156,12 +160,12 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding, // for 0xFFFF-0xFFFF range. // (e.g., many fonts in http://www.princexml.com/fonts/) if (i == segcount - 1u) { - OTS_WARNING("bad id_range_offset"); + 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); + return Error("Bad segment offset (%d)", ranges[i].id_range_offset); } } } @@ -176,36 +180,36 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding, (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"); + 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); + return Error("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); + return Error("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 && + if (os2->table.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; + os2->table.first_char_index > ranges[i].start_range) { + os2->table.first_char_index = ranges[i].start_range; } - if (font->os2->last_char_index != 0xFFFF && + if (os2->table.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; + os2->table.last_char_index < ranges[i].end_range) { + os2->table.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)", + return Error("Final segment start and end must be 0xFFFF (0x%04X-0x%04X)", ranges[segcount - 1].start_range, ranges[segcount - 1].end_range); } @@ -219,7 +223,7 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding, // 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); + return Error("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1); } } else { const uint16_t range_delta = code_point - ranges[i].start_range; @@ -230,13 +234,13 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding, 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); + return Error("bad glyph id offset (%d > %ld)", glyph_id_offset, length); } uint16_t glyph; std::memcpy(&glyph, data + glyph_id_offset, 2); - glyph = ntohs(glyph); + glyph = ots_ntohs(glyph); if (glyph >= num_glyphs) { - return OTS_FAILURE_MSG("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1); + return Error("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1); } } } @@ -245,85 +249,85 @@ bool ParseFormat4(ots::Font *font, int platform, int encoding, // 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; + this->subtable_3_0_4_data = data; + this->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; + this->subtable_3_1_4_data = data; + this->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; + this->subtable_0_3_4_data = data; + this->subtable_0_3_4_length = length; } else { - return OTS_FAILURE_MSG("Unknown cmap subtable type (platform=%d, encoding=%d)", platform, encoding); + return Error("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) { +bool OpenTypeCMAP::Parse31012(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"); + return Error("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"); + return Error("can't read format 12 subtable language"); } if (language) { - return OTS_FAILURE_MSG("format 12 subtable language should be zero (%d)", language); + return Error("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"); + return Error("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); + return Error("Bad format 12 subtable group count %d", num_groups); } std::vector &groups - = font->cmap->subtable_3_10_12; + = this->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"); + return Error("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)", + return Error("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)", + return Error("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); + return Error("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)", + return Error("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)", + return Error("overlapping format 12 subtable groups (startCharCode=0x%4X <= endCharCode=0x%4X of previous group)", groups[i].start_range, groups[i-1].end_range); } } @@ -331,44 +335,43 @@ bool Parse31012(ots::Font *font, return true; } -bool Parse31013(ots::Font *font, - const uint8_t *data, size_t length, uint16_t num_glyphs) { +bool OpenTypeCMAP::Parse31013(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"); + return Error("Bad cmap subtable length"); } uint32_t language = 0; if (!subtable.ReadU32(&language)) { - return OTS_FAILURE_MSG("Can't read cmap subtable language"); + return Error("Can't read cmap subtable language"); } if (language) { - return OTS_FAILURE_MSG("Cmap subtable language should be zero but is %d", language); + return Error("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"); + return Error("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); + return Error("Bad format 13 subtable group count %d", num_groups); } - std::vector &groups - = font->cmap->subtable_3_10_13; + std::vector &groups = this->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"); + return Error("Can't read subrange structure in a cmap subtable"); } // We conservatively limit all of the values to protect some parsers from @@ -376,29 +379,29 @@ bool Parse31013(ots::Font *font, 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); + return Error("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); + return Error("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); + return Error("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 Error("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) { +bool OpenTypeCMAP::Parse0514(const uint8_t *data, size_t length, + uint16_t num_glyphs) { // Unicode Variation Selector table ots::Buffer subtable(data, length); @@ -407,26 +410,26 @@ bool Parse0514(ots::Font *font, // Skip format (USHORT) and length (ULONG) if (!subtable.Skip(6)) { - return OTS_FAILURE_MSG("Can't read start of cmap subtable"); + return Error("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"); + return Error("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); + return Error("Bad format 14 subtable records count %d", num_records); } std::vector& records - = font->cmap->subtable_0_5_14; + = this->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); + return Error("Can't read record structure of record %d in cmap subtale", i); } // Checks the value of variation selector if (!((records[i].var_selector >= kMongolianVSStart && @@ -435,24 +438,24 @@ bool Parse0514(ots::Font *font, 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); + return Error("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); + return Error("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); + return Error("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); + return Error("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); + return Error("Non default offset too high (%d >= %ld) in record %d", records[i].non_default_offset, length, i); } } @@ -462,10 +465,10 @@ bool Parse0514(ots::Font *font, 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); + return Error("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); + return Error("Bad number of ranges (%d) in record %d", num_ranges, i); } uint32_t last_unicode_value = 0; @@ -476,7 +479,7 @@ bool Parse0514(ots::Font *font, 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); + return Error("Can't read range info in variation selector record %d", i); } const uint32_t check_value = ranges[j].unicode_value + ranges[j].additional_count; @@ -485,7 +488,7 @@ bool Parse0514(ots::Font *font, 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); + return Error("Bad Unicode value *%04X) in variation selector range %d record %d", ranges[j].unicode_value, j, i); } last_unicode_value = check_value; } @@ -496,10 +499,10 @@ bool Parse0514(ots::Font *font, 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); + return Error("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); + return Error("Bad number of mappings (%d) in variation selector record %d", num_mappings, i); } uint32_t last_unicode_value = 0; @@ -510,14 +513,14 @@ bool Parse0514(ots::Font *font, 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); + return Error("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); + return Error("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; } @@ -525,60 +528,55 @@ bool Parse0514(ots::Font *font, } if (subtable.offset() != length) { - return OTS_FAILURE_MSG("Bad subtable offset (%ld != %ld)", subtable.offset(), length); + return Error("Bad subtable offset (%ld != %ld)", subtable.offset(), length); } - font->cmap->subtable_0_5_14_length = subtable.offset(); + this->subtable_0_5_14_length = subtable.offset(); return true; } -bool Parse100(ots::Font *font, const uint8_t *data, size_t length) { +bool OpenTypeCMAP::Parse100(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"); + return Error("Bad cmap subtable"); } uint16_t language = 0; if (!subtable.ReadU16(&language)) { - return OTS_FAILURE_MSG("Can't read language in cmap subtable"); + return Error("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); + Warning("language id should be zero: %u", language); } - font->cmap->subtable_1_0_0.reserve(kFormat0ArraySize); + this->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); + return Error("Can't read glyph id at array[%ld] in cmap subtable", i); } - font->cmap->subtable_1_0_0.push_back(glyph_id); + this->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) { +bool OpenTypeCMAP::Parse(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"); + return Error("Can't read structure of cmap"); } if (version != 0) { - return OTS_FAILURE_MSG("Non zero cmap version (%d)", version); + return Error("Non zero cmap version (%d)", version); } if (!num_tables) { - return OTS_FAILURE_MSG("No subtables in cmap!"); + return Error("No subtables in cmap!"); } std::vector subtable_headers; @@ -591,7 +589,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { 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); + return Error("Can't read subtable information cmap subtable %d", i); } subtable_headers.push_back(subt); @@ -602,11 +600,11 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { // 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); + return Error("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); + return Error("Bad subtable offset (%d) in cmap subtable %d", subtable_headers[i].offset, i); } } @@ -615,7 +613,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { 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); + return Error("Can't read cmap subtable header format %d", i); } uint16_t len = 0; @@ -624,10 +622,10 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { case 0: case 4: if (!table.ReadU16(&len)) { - return OTS_FAILURE_MSG("Can't read cmap subtable %d length", i); + return Error("Can't read cmap subtable %d length", i); } if (!table.ReadU16(&lang)) { - return OTS_FAILURE_MSG("Can't read cmap subtable %d language", i); + return Error("Can't read cmap subtable %d language", i); } subtable_headers[i].length = len; subtable_headers[i].language = lang; @@ -635,18 +633,18 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { case 12: case 13: if (!table.Skip(2)) { - return OTS_FAILURE_MSG("Bad cmap subtable %d structure", i); + return Error("Bad cmap subtable %d structure", i); } if (!table.ReadU32(&subtable_headers[i].length)) { - return OTS_FAILURE_MSG("Can read cmap subtable %d length", i); + return Error("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); + return Error("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); + return Error("Can't read cmap subtable %d length", i); } subtable_headers[i].language = 0; break; @@ -664,7 +662,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { (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 " + 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, @@ -679,14 +677,14 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { 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); + return Error("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); + return Error("Over long cmap subtable %d @ %d for %d", i, subtable_headers[i].offset, subtable_headers[i].length); } } @@ -714,16 +712,18 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { 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); + return Error("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."); + OpenTypeMAXP *maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("No maxp table in font! Needed by cmap."); } - const uint16_t num_glyphs = font->maxp->num_glyphs; + const uint16_t num_glyphs = 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 @@ -760,29 +760,30 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { // 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, + if (!ParseFormat4(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); + return Error("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, + if (!ParseFormat4(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); + return Error("Failed to parse format 4 cmap subtable %d", i); } - } else if ((subtable_headers[i].encoding == 3) && + } else if ((subtable_headers[i].encoding == 3 || + subtable_headers[i].encoding == 4) && (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, + // parse and output the 0-3-12 or 0-4-12 tables as 3-10-12 table. + if (!Parse31012(data + subtable_headers[i].offset, subtable_headers[i].length, num_glyphs)) { - return OTS_FAILURE_MSG("Failed to parse format 12 cmap subtable %d", i); + return Error("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, + if (!Parse0514(data + subtable_headers[i].offset, subtable_headers[i].length, num_glyphs)) { - return OTS_FAILURE_MSG("Failed to parse format 14 cmap subtable %d", i); + return Error("Failed to parse format 14 cmap subtable %d", i); } } } else if (subtable_headers[i].platform == 1) { @@ -791,7 +792,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { 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, + if (!Parse100(data + subtable_headers[i].offset, subtable_headers[i].length)) { return OTS_FAILURE(); } @@ -804,7 +805,7 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { case 1: if (subtable_headers[i].format == 4) { // parse 3-0-4 or 3-1-4 table. - if (!ParseFormat4(font, subtable_headers[i].platform, + if (!ParseFormat4(subtable_headers[i].platform, subtable_headers[i].encoding, data + subtable_headers[i].offset, subtable_headers[i].length, num_glyphs)) { @@ -814,14 +815,14 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { break; case 10: if (subtable_headers[i].format == 12) { - font->cmap->subtable_3_10_12.clear(); - if (!Parse31012(font, data + subtable_headers[i].offset, + this->subtable_3_10_12.clear(); + if (!Parse31012(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, + this->subtable_3_10_13.clear(); + if (!Parse31013(data + subtable_headers[i].offset, subtable_headers[i].length, num_glyphs)) { return OTS_FAILURE(); } @@ -834,20 +835,16 @@ bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { 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; +bool OpenTypeCMAP::Serialize(OTSStream *out) { + const bool have_034 = this->subtable_0_3_4_data != NULL; + const bool have_0514 = this->subtable_0_5_14.size() != 0; + const bool have_100 = this->subtable_1_0_0.size() != 0; + const bool have_304 = this->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 bool have_314 = (!have_304) && this->subtable_3_1_4_data; + const bool have_31012 = this->subtable_3_10_12.size() != 0; + const bool have_31013 = this->subtable_3_10_13.size() != 0; const uint16_t num_subtables = static_cast(have_034) + static_cast(have_0514) + static_cast(have_100) + @@ -860,7 +857,7 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) { // 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"); + return Error("no supported subtables were found"); } if (!out->WriteU16(0) || @@ -875,8 +872,8 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) { 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)) { + if (!out->Write(this->subtable_0_3_4_data, + this->subtable_0_3_4_length)) { return OTS_FAILURE(); } } @@ -884,10 +881,10 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) { const off_t offset_0514 = out->Tell(); if (have_0514) { const std::vector &records - = font->cmap->subtable_0_5_14; + = this->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(this->subtable_0_5_14_length) || !out->WriteU32(num_records)) { return OTS_FAILURE(); } @@ -939,23 +936,23 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) { !out->WriteU16(0)) { // language return OTS_FAILURE(); } - if (!out->Write(&(font->cmap->subtable_1_0_0[0]), kFormat0ArraySize)) { + if (!out->Write(&(this->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)) { + if (!out->Write(this->subtable_3_0_4_data, + this->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)) { + if (!out->Write(this->subtable_3_1_4_data, + this->subtable_3_1_4_length)) { return OTS_FAILURE(); } } @@ -963,7 +960,7 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) { const off_t offset_31012 = out->Tell(); if (have_31012) { std::vector &groups - = font->cmap->subtable_3_10_12; + = this->subtable_3_10_12; const unsigned num_groups = groups.size(); if (!out->WriteU16(12) || !out->WriteU16(0) || @@ -985,7 +982,7 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) { const off_t offset_31013 = out->Tell(); if (have_31013) { std::vector &groups - = font->cmap->subtable_3_10_13; + = this->subtable_3_10_13; const unsigned num_groups = groups.size(); if (!out->WriteU16(13) || !out->WriteU16(0) || @@ -1074,15 +1071,4 @@ bool ots_cmap_serialise(OTSStream *out, Font *font) { 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 index 5b09556b7..feddbc696 100644 --- a/gfx/ots/src/cmap.h +++ b/gfx/ots/src/cmap.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -35,9 +35,11 @@ struct OpenTypeCMAPSubtableVSRecord { std::vector mappings; }; -struct OpenTypeCMAP { - OpenTypeCMAP() - : subtable_0_3_4_data(NULL), +class OpenTypeCMAP : public Table { + public: + explicit OpenTypeCMAP(Font *font, uint32_t tag) + : Table(font, tag, tag), + subtable_0_3_4_data(NULL), subtable_0_3_4_length(0), subtable_0_5_14_length(0), subtable_3_0_4_data(NULL), @@ -46,6 +48,10 @@ struct OpenTypeCMAP { subtable_3_1_4_length(0) { } + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + private: // Platform 0, Encoding 3, Format 4, Unicode BMP table. const uint8_t *subtable_0_3_4_data; size_t subtable_0_3_4_length; @@ -67,6 +73,13 @@ struct OpenTypeCMAP { std::vector subtable_3_10_13; // Platform 1, Encoding 0, Format 0, Mac Roman table. std::vector subtable_1_0_0; + + bool ParseFormat4(int platform, int encoding, const uint8_t *data, + size_t length, uint16_t num_glyphs); + bool Parse31012(const uint8_t *data, size_t length, uint16_t num_glyphs); + bool Parse31013(const uint8_t *data, size_t length, uint16_t num_glyphs); + bool Parse0514(const uint8_t *data, size_t length, uint16_t num_glyphs); + bool Parse100(const uint8_t *data, size_t length); }; } // namespace ots diff --git a/gfx/ots/src/cvar.cc b/gfx/ots/src/cvar.cc new file mode 100644 index 000000000..a2bad7a15 --- /dev/null +++ b/gfx/ots/src/cvar.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2018 The OTS 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 "cvar.h" + +#include "fvar.h" +#include "variations.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeCVAR +// ----------------------------------------------------------------------------- + +bool OpenTypeCVAR::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + + uint16_t majorVersion; + uint16_t minorVersion; + + if (!table.ReadU16(&majorVersion) || + !table.ReadU16(&minorVersion)) { + return Drop("Failed to read table header"); + } + + if (majorVersion != 1) { + return Drop("Unknown table version"); + } + + OpenTypeFVAR* fvar = static_cast( + GetFont()->GetTypedTable(OTS_TAG_FVAR)); + if (!fvar) { + return DropVariations("Required fvar table is missing"); + } + + if (!ParseVariationData(GetFont(), data + table.offset(), length - table.offset(), + fvar->AxisCount(), 0)) { + return Drop("Failed to parse variation data"); + } + + this->m_data = data; + this->m_length = length; + + return true; +} + +bool OpenTypeCVAR::Serialize(OTSStream* out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write cvar table"); + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/cvar.h b/gfx/ots/src/cvar.h new file mode 100644 index 000000000..8f31e98cd --- /dev/null +++ b/gfx/ots/src/cvar.h @@ -0,0 +1,31 @@ +// Copyright (c) 2018 The OTS 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_CVAR_H_ +#define OTS_CVAR_H_ + +#include "ots.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeCVAR Interface +// ----------------------------------------------------------------------------- + +class OpenTypeCVAR : public Table { + public: + explicit OpenTypeCVAR(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + const uint8_t *m_data; + size_t m_length; +}; + +} // namespace ots + +#endif // OTS_CVAR_H_ diff --git a/gfx/ots/src/cvt.cc b/gfx/ots/src/cvt.cc index 1402e7c06..2e0257889 100644 --- a/gfx/ots/src/cvt.cc +++ b/gfx/ots/src/cvt.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,59 +7,40 @@ // 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) { +bool OpenTypeCVT::Parse(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. + return Error("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); + return Error("Uneven table length (%d)", length); } if (!table.Skip(length)) { - return OTS_FAILURE_MSG("Length too high"); + return Error("Table length too high"); } - cvt->data = data; - cvt->length = length; + this->data = data; + this->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"); +bool OpenTypeCVT::Serialize(OTSStream *out) { + if (!out->Write(this->data, this->length)) { + return Error("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; +bool OpenTypeCVT::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/cvt.h b/gfx/ots/src/cvt.h index 3c25f06f6..88a96ca20 100644 --- a/gfx/ots/src/cvt.h +++ b/gfx/ots/src/cvt.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,7 +9,16 @@ namespace ots { -struct OpenTypeCVT { +class OpenTypeCVT : public Table { + public: + explicit OpenTypeCVT(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); + + private: const uint8_t *data; uint32_t length; }; diff --git a/gfx/ots/src/feat.cc b/gfx/ots/src/feat.cc new file mode 100644 index 000000000..374a7ae9f --- /dev/null +++ b/gfx/ots/src/feat.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2009-2017 The OTS 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 "feat.h" + +#include "name.h" + +namespace ots { + +bool OpenTypeFEAT::Parse(const uint8_t* data, size_t length) { + if (GetFont()->dropped_graphite) { + return Drop("Skipping Graphite table"); + } + Buffer table(data, length); + + if (!table.ReadU32(&this->version)) { + return DropGraphite("Failed to read version"); + } + if (this->version >> 16 != 1 && this->version >> 16 != 2) { + return DropGraphite("Unsupported table version: %u", this->version >> 16); + } + if (!table.ReadU16(&this->numFeat)) { + return DropGraphite("Failed to read numFeat"); + } + if (!table.ReadU16(&this->reserved)) { + return DropGraphite("Failed to read reserved"); + } + if (this->reserved != 0) { + Warning("Nonzero reserved"); + } + if (!table.ReadU32(&this->reserved2)) { + return DropGraphite("Failed to read valid reserved2"); + } + if (this->reserved2 != 0) { + Warning("Nonzero reserved2"); + } + + std::unordered_set unverified; + //this->features.resize(this->numFeat, this); + for (unsigned i = 0; i < this->numFeat; ++i) { + this->features.emplace_back(this); + FeatureDefn& feature = this->features[i]; + if (!feature.ParsePart(table)) { + return DropGraphite("Failed to read features[%u]", i); + } + this->feature_ids.insert(feature.id); + for (unsigned j = 0; j < feature.numSettings; ++j) { + size_t offset = feature.offset + j * 4; + if (offset < feature.offset || offset > length) { + return DropGraphite("Invalid FeatSettingDefn offset %zu/%zu", + offset, length); + } + unverified.insert(offset); + // need to verify that this FeatureDefn points to valid + // FeatureSettingDefn + } + } + + while (table.remaining()) { + bool used = unverified.erase(table.offset()); + FeatureSettingDefn featSetting(this); + if (!featSetting.ParsePart(table, used)) { + return DropGraphite("Failed to read a FeatureSettingDefn"); + } + featSettings.push_back(featSetting); + } + + if (!unverified.empty()) { + return DropGraphite("%zu incorrect offsets into featSettings", + unverified.size()); + } + if (table.remaining()) { + return Warning("%zu bytes unparsed", table.remaining()); + } + return true; +} + +bool OpenTypeFEAT::Serialize(OTSStream* out) { + if (!out->WriteU32(this->version) || + !out->WriteU16(this->numFeat) || + !out->WriteU16(this->reserved) || + !out->WriteU32(this->reserved2) || + !SerializeParts(this->features, out) || + !SerializeParts(this->featSettings, out)) { + return Error("Failed to write table"); + } + return true; +} + +bool OpenTypeFEAT::IsValidFeatureId(uint32_t id) const { + return feature_ids.count(id); +} + +bool OpenTypeFEAT::FeatureDefn::ParsePart(Buffer& table) { + OpenTypeNAME* name = static_cast( + parent->GetFont()->GetTypedTable(OTS_TAG_NAME)); + if (!name) { + return parent->Error("FeatureDefn: Required name table is missing"); + } + + if (parent->version >> 16 >= 2 && !table.ReadU32(&this->id)) { + return parent->Error("FeatureDefn: Failed to read id"); + } + if (parent->version >> 16 == 1) { + uint16_t id; + if (!table.ReadU16(&id)) { + return parent->Error("FeatureDefn: Failed to read id"); + } + this->id = id; + } + if (!table.ReadU16(&this->numSettings)) { + return parent->Error("FeatureDefn: Failed to read numSettings"); + } + if (parent->version >> 16 >= 2) { + if (!table.ReadU16(&this->reserved)) { + return parent->Error("FeatureDefn: Failed to read reserved"); + } + if (this->reserved != 0) { + parent->Warning("FeatureDefn: Nonzero reserved"); + } + } + if (!table.ReadU32(&this->offset)) { + return parent->Error("FeatureDefn: Failed to read offset"); + } // validity of offset verified in OpenTypeFEAT::Parse + if (!table.ReadU16(&this->flags)) { + return parent->Error("FeatureDefn: Failed to read flags"); + } + if ((this->flags & RESERVED) != 0) { + this->flags &= ~RESERVED; + parent->Warning("FeatureDefn: Nonzero (flags & 0x%x) repaired", RESERVED); + } + if (this->flags & HAS_DEFAULT_SETTING && + (this->flags & DEFAULT_SETTING) >= this->numSettings) { + return parent->Error("FeatureDefn: (flags & 0x%x) is set but (flags & 0x%x " + "is not a valid setting index", HAS_DEFAULT_SETTING, + DEFAULT_SETTING); + } + if (!table.ReadU16(&this->label)) { + return parent->Error("FeatureDefn: Failed to read label"); + } + if (!name->IsValidNameId(this->label)) { + if (this->id == 1 && name->IsValidNameId(this->label, true)) { + parent->Warning("FeatureDefn: Missing NameRecord repaired for feature" + " with id=%u, label=%u", this->id, this->label); + } + else { + return parent->Error("FeatureDefn: Invalid label"); + } + } + return true; +} + +bool OpenTypeFEAT::FeatureDefn::SerializePart(OTSStream* out) const { + if ((parent->version >> 16 >= 2 && !out->WriteU32(this->id)) || + (parent->version >> 16 == 1 && + !out->WriteU16(static_cast(this->id))) || + !out->WriteU16(this->numSettings) || + (parent->version >> 16 >= 2 && !out->WriteU16(this->reserved)) || + !out->WriteU32(this->offset) || + !out->WriteU16(this->flags) || + !out->WriteU16(this->label)) { + return parent->Error("FeatureDefn: Failed to write"); + } + return true; +} + +bool OpenTypeFEAT::FeatureSettingDefn::ParsePart(Buffer& table, bool used) { + OpenTypeNAME* name = static_cast( + parent->GetFont()->GetTypedTable(OTS_TAG_NAME)); + if (!name) { + return parent->Error("FeatureSettingDefn: Required name table is missing"); + } + + if (!table.ReadS16(&this->value)) { + return parent->Error("FeatureSettingDefn: Failed to read value"); + } + if (!table.ReadU16(&this->label) || + (used && !name->IsValidNameId(this->label))) { + return parent->Error("FeatureSettingDefn: Failed to read valid label"); + } + return true; +} + +bool OpenTypeFEAT::FeatureSettingDefn::SerializePart(OTSStream* out) const { + if (!out->WriteS16(this->value) || + !out->WriteU16(this->label)) { + return parent->Error("FeatureSettingDefn: Failed to write"); + } + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/feat.h b/gfx/ots/src/feat.h new file mode 100644 index 000000000..29c2656ff --- /dev/null +++ b/gfx/ots/src/feat.h @@ -0,0 +1,61 @@ +// Copyright (c) 2009-2017 The OTS 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_FEAT_H_ +#define OTS_FEAT_H_ + +#include +#include + +#include "ots.h" +#include "graphite.h" + +namespace ots { + +class OpenTypeFEAT : public Table { + public: + explicit OpenTypeFEAT(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + bool IsValidFeatureId(uint32_t id) const; + + private: + struct FeatureDefn : public TablePart { + explicit FeatureDefn(OpenTypeFEAT* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + uint32_t id; + uint16_t numSettings; + uint16_t reserved; + uint32_t offset; + uint16_t flags; + static const uint16_t HAS_DEFAULT_SETTING = 0x4000; + static const uint16_t RESERVED = 0x3F00; + static const uint16_t DEFAULT_SETTING = 0x00FF; + uint16_t label; + }; + struct FeatureSettingDefn : public TablePart { + explicit FeatureSettingDefn(OpenTypeFEAT* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table) { return ParsePart(table, true); } + bool ParsePart(Buffer& table, bool used); + bool SerializePart(OTSStream* out) const; + int16_t value; + uint16_t label; + }; + uint32_t version; + uint16_t numFeat; + uint16_t reserved; + uint32_t reserved2; + std::vector features; + std::vector featSettings; + std::unordered_set feature_ids; +}; + +} // namespace ots + +#endif // OTS_FEAT_H_ diff --git a/gfx/ots/src/fpgm.cc b/gfx/ots/src/fpgm.cc index faa9a2392..bb52b367f 100644 --- a/gfx/ots/src/fpgm.cc +++ b/gfx/ots/src/fpgm.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,53 +7,36 @@ // 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) { +bool OpenTypeFPGM::Parse(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. + return Error("length (%ld) > 120", length); // almost all fpgm tables are less than 5k bytes. } if (!table.Skip(length)) { - return OTS_FAILURE_MSG("Bad fpgm length"); + return Error("Bad table length"); } - fpgm->data = data; - fpgm->length = length; + this->data = data; + this->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"); +bool OpenTypeFPGM::Serialize(OTSStream *out) { + if (!out->Write(this->data, this->length)) { + return Error("Failed to write fpgm table"); } 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; +bool OpenTypeFPGM::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/fpgm.h b/gfx/ots/src/fpgm.h index 8fabac36d..9ed6b34bf 100644 --- a/gfx/ots/src/fpgm.h +++ b/gfx/ots/src/fpgm.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,7 +9,16 @@ namespace ots { -struct OpenTypeFPGM { +class OpenTypeFPGM : public Table { + public: + explicit OpenTypeFPGM(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); + + private: const uint8_t *data; uint32_t length; }; diff --git a/gfx/ots/src/fvar.cc b/gfx/ots/src/fvar.cc new file mode 100644 index 000000000..6f9b4d6eb --- /dev/null +++ b/gfx/ots/src/fvar.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2018 The OTS 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 "fvar.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeFVAR +// ----------------------------------------------------------------------------- + +bool OpenTypeFVAR::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + if (!table.ReadU16(&this->majorVersion) || + !table.ReadU16(&this->minorVersion) || + !table.ReadU16(&this->axesArrayOffset) || + !table.ReadU16(&this->reserved) || + !table.ReadU16(&this->axisCount) || + !table.ReadU16(&this->axisSize) || + !table.ReadU16(&this->instanceCount) || + !table.ReadU16(&this->instanceSize)) { + return DropVariations("Failed to read table header"); + } + if (this->majorVersion != 1) { + return DropVariations("Unknown table version"); + } + if (this->minorVersion > 0) { + Warning("Downgrading minor version to 0"); + this->minorVersion = 0; + } + if (this->axesArrayOffset > length || this->axesArrayOffset < table.offset()) { + return DropVariations("Bad axesArrayOffset"); + } + if (this->reserved != 2) { + Warning("Expected reserved=2"); + this->reserved = 2; + } + if (this->axisCount == 0) { + return DropVariations("No variation axes"); + } + if (this->axisSize != 20) { + return DropVariations("Invalid axisSize"); + } + // instanceCount is not validated + if (this->instanceSize == this->axisCount * sizeof(Fixed) + 6) { + this->instancesHavePostScriptNameID = true; + } else if (this->instanceSize == this->axisCount * sizeof(Fixed) + 4) { + this->instancesHavePostScriptNameID = false; + } else { + return DropVariations("Invalid instanceSize"); + } + + // When we serialize, the axes array will go here, even if it was + // originally at a different offset. So we update the axesArrayOffset + // field for the header. + uint32_t origAxesArrayOffset = this->axesArrayOffset; + this->axesArrayOffset = table.offset(); + + table.set_offset(origAxesArrayOffset); + for (unsigned i = 0; i < this->axisCount; i++) { + this->axes.emplace_back(); + auto& axis = this->axes[i]; + if (!table.ReadU32(&axis.axisTag) || + !table.ReadS32(&axis.minValue) || + !table.ReadS32(&axis.defaultValue) || + !table.ReadS32(&axis.maxValue) || + !table.ReadU16(&axis.flags) || + !table.ReadU16(&axis.axisNameID)) { + return DropVariations("Failed to read axis record"); + } + if (!CheckTag(axis.axisTag)) { + return DropVariations("Bad axis tag"); + } + if (!(axis.minValue <= axis.defaultValue && axis.defaultValue <= axis.maxValue)) { + return DropVariations("Bad axis value range"); + } + if ((axis.flags & 0xFFFEu) != 0) { + Warning("Discarding unknown axis flags"); + axis.flags &= ~0xFFFEu; + } + if (axis.axisNameID <= 255 || axis.axisNameID >= 32768) { + Warning("Axis nameID out of range"); + // We don't check that the name actually exists -- assume the client can handle + // a missing name when it tries to read the table. + } + } + + for (unsigned i = 0; i < this->instanceCount; i++) { + this->instances.emplace_back(); + auto& inst = this->instances[i]; + if (!table.ReadU16(&inst.subfamilyNameID) || + !table.ReadU16(&inst.flags)) { + return DropVariations("Failed to read instance record"); + } + inst.coordinates.reserve(this->axisCount); + for (unsigned j = 0; j < this->axisCount; j++) { + inst.coordinates.emplace_back(); + auto& coord = inst.coordinates[j]; + if (!table.ReadS32(&coord)) { + return DropVariations("Failed to read instance coordinates"); + } + } + if (this->instancesHavePostScriptNameID) { + if (!table.ReadU16(&inst.postScriptNameID)) { + return DropVariations("Failed to read instance psname ID"); + } + } + } + + if (table.remaining()) { + return Warning("%zu bytes unparsed", table.remaining()); + } + + return true; +} + +bool OpenTypeFVAR::Serialize(OTSStream* out) { + if (!out->WriteU16(this->majorVersion) || + !out->WriteU16(this->minorVersion) || + !out->WriteU16(this->axesArrayOffset) || + !out->WriteU16(this->reserved) || + !out->WriteU16(this->axisCount) || + !out->WriteU16(this->axisSize) || + !out->WriteU16(this->instanceCount) || + !out->WriteU16(this->instanceSize)) { + return Error("Failed to write table"); + } + + for (unsigned i = 0; i < this->axisCount; i++) { + const auto& axis = this->axes[i]; + if (!out->WriteU32(axis.axisTag) || + !out->WriteS32(axis.minValue) || + !out->WriteS32(axis.defaultValue) || + !out->WriteS32(axis.maxValue) || + !out->WriteU16(axis.flags) || + !out->WriteU16(axis.axisNameID)) { + return Error("Failed to write table"); + } + } + + for (unsigned i = 0; i < this->instanceCount; i++) { + const auto& inst = this->instances[i]; + if (!out->WriteU16(inst.subfamilyNameID) || + !out->WriteU16(inst.flags)) { + return Error("Failed to write table"); + } + for (unsigned j = 0; j < this->axisCount; j++) { + const auto& coord = inst.coordinates[j]; + if (!out->WriteS32(coord)) { + return Error("Failed to write table"); + } + } + if (this->instancesHavePostScriptNameID) { + if (!out->WriteU16(inst.postScriptNameID)) { + return Error("Failed to write table"); + } + } + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/fvar.h b/gfx/ots/src/fvar.h new file mode 100644 index 000000000..a469c8cdd --- /dev/null +++ b/gfx/ots/src/fvar.h @@ -0,0 +1,63 @@ +// Copyright (c) 2018 The OTS 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_FVAR_H_ +#define OTS_FVAR_H_ + +#include + +#include "ots.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeFVAR Interface +// ----------------------------------------------------------------------------- + +class OpenTypeFVAR : public Table { + public: + explicit OpenTypeFVAR(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + uint16_t AxisCount() const { return axisCount; } + + private: + uint16_t majorVersion; + uint16_t minorVersion; + uint16_t axesArrayOffset; + uint16_t reserved; + uint16_t axisCount; + uint16_t axisSize; + uint16_t instanceCount; + uint16_t instanceSize; + + typedef int32_t Fixed; /* 16.16 fixed-point value */ + + struct VariationAxisRecord { + uint32_t axisTag; + Fixed minValue; + Fixed defaultValue; + Fixed maxValue; + uint16_t flags; + uint16_t axisNameID; + }; + std::vector axes; + + struct InstanceRecord { + uint16_t subfamilyNameID; + uint16_t flags; + std::vector coordinates; + uint16_t postScriptNameID; // optional + }; + std::vector instances; + + bool instancesHavePostScriptNameID; +}; + +} // namespace ots + +#endif // OTS_FVAR_H_ diff --git a/gfx/ots/src/gasp.cc b/gfx/ots/src/gasp.cc index 5ebf5d84b..2a03c831f 100644 --- a/gfx/ots/src/gasp.cc +++ b/gfx/ots/src/gasp.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,113 +7,78 @@ // 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) { +bool OpenTypeGASP::Parse(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) || + if (!table.ReadU16(&this->version) || !table.ReadU16(&num_ranges)) { - return OTS_FAILURE_MSG("Failed to read table header"); + return Error("Failed to read table header"); } - if (gasp->version > 1) { + if (this->version > 1) { // Lots of Linux fonts have bad version numbers... - DROP_THIS_TABLE("bad version: %u", gasp->version); - return true; + return Drop("Unsupported version: %u", this->version); } if (num_ranges == 0) { - DROP_THIS_TABLE("num_ranges is zero"); - return true; + return Drop("numRanges is zero"); } - gasp->gasp_ranges.reserve(num_ranges); + this->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); + return Error("Failed to read GASPRANGE %d", i); } - if ((i > 0) && (gasp->gasp_ranges[i - 1].first >= max_ppem)) { + if ((i > 0) && (this->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; + return Drop("Ranges are not sorted"); } if ((i == num_ranges - 1u) && // never underflow. (max_ppem != 0xffffu)) { - DROP_THIS_TABLE("The last record should be 0xFFFF as a sentinel value " + return Drop("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); + 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; + if (this->version == 0 && (behavior >> 2) != 0) { + Warning("Changed the version number to 1"); + this->version = 1; } - gasp->gasp_ranges.push_back(std::make_pair(max_ppem, behavior)); + this->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(gasp->gasp_ranges.size()); - if (num_ranges != gasp->gasp_ranges.size() || - !out->WriteU16(gasp->version) || +bool OpenTypeGASP::Serialize(OTSStream *out) { + const uint16_t num_ranges = static_cast(this->gasp_ranges.size()); + if (num_ranges != this->gasp_ranges.size() || + !out->WriteU16(this->version) || !out->WriteU16(num_ranges)) { - return OTS_FAILURE_MSG("failed to write gasp header"); + return Error("Failed to write table 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); + if (!out->WriteU16(this->gasp_ranges[i].first) || + !out->WriteU16(this->gasp_ranges[i].second)) { + return Error("Failed to write GASPRANGE %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 index 48d7e7c16..ce9e987aa 100644 --- a/gfx/ots/src/gasp.h +++ b/gfx/ots/src/gasp.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,7 +13,15 @@ namespace ots { -struct OpenTypeGASP { +class OpenTypeGASP : public Table { + public: + explicit OpenTypeGASP(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + private: uint16_t version; // A array of (max PPEM, GASP behavior) pairs. std::vector > gasp_ranges; diff --git a/gfx/ots/src/gdef.cc b/gfx/ots/src/gdef.cc index 71c6fc592..71e1075e3 100644 --- a/gfx/ots/src/gdef.cc +++ b/gfx/ots/src/gdef.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -11,68 +11,60 @@ #include "gsub.h" #include "layout.h" #include "maxp.h" +#include "variations.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); -} +const uint16_t kMaxCaretValueFormat = 3; + +} // namespace -bool ParseAttachListTable(ots::Font *font, const uint8_t *data, - size_t length, const uint16_t num_glyphs) { +namespace ots { + +bool OpenTypeGDEF::ParseAttachListTable(const uint8_t *data, size_t length) { 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"); + return Error("Failed to read gdef header"); } const unsigned attach_points_end = 2 * static_cast(glyph_count) + 4; if (attach_points_end > std::numeric_limits::max()) { - return OTS_FAILURE_MSG("Bad glyph count in gdef"); + return Error("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); + return Error("Bad coverage offset %d", offset_coverage); } - if (glyph_count > num_glyphs) { - return OTS_FAILURE_MSG("Bad glyph count %u", glyph_count); + if (glyph_count > this->m_num_glyphs) { + return Error("Bad glyph count %u", glyph_count); } std::vector 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); + return Error("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]); + return Error("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"); + if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, + length - offset_coverage, this->m_num_glyphs)) { + return Error("Bad coverage table"); } // Parse attach point table @@ -80,20 +72,20 @@ bool ParseAttachListTable(ots::Font *font, const uint8_t *data, 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); + return Error("Can't read point count %d", i); } if (point_count == 0) { - return OTS_FAILURE_MSG("zero point count %d", i); + return Error("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); + return Error("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", + return Error("bad contour indeces: %u >= %u", last_point_index, point_index); } last_point_index = point_index; @@ -102,43 +94,42 @@ bool ParseAttachListTable(ots::Font *font, const uint8_t *data, return true; } -bool ParseLigCaretListTable(ots::Font *font, const uint8_t *data, - size_t length, const uint16_t num_glyphs) { +bool OpenTypeGDEF::ParseLigCaretListTable(const uint8_t *data, size_t length) { 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"); + return Error("Can't read caret structure"); } const unsigned lig_glyphs_end = 2 * static_cast(lig_glyph_count) + 4; if (lig_glyphs_end > std::numeric_limits::max()) { - return OTS_FAILURE_MSG("Bad caret structure"); + return Error("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); + return Error("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); + if (lig_glyph_count > this->m_num_glyphs) { + return Error("bad ligature glyph count: %u", lig_glyph_count); } std::vector 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); + return Error("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); + return Error("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"); + if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, + length - offset_coverage, this->m_num_glyphs)) { + return Error("Can't parse caret coverage table"); } // Parse ligature glyph table @@ -146,10 +137,10 @@ bool ParseLigCaretListTable(ots::Font *font, const uint8_t *data, 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); + return Error("Can't read caret count for glyph %d", i); } if (caret_count == 0) { - return OTS_FAILURE_MSG("bad caret value count: %u", caret_count); + return Error("bad caret value count: %u", caret_count); } std::vector caret_value_offsets; @@ -157,10 +148,10 @@ bool ParseLigCaretListTable(ots::Font *font, const uint8_t *data, unsigned caret_value_offsets_end = 2 * static_cast(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); + return Error("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); + return Error("Bad caret offset %d for caret %d glyph %d", caret_value_offsets[j], j, i); } } @@ -169,91 +160,93 @@ bool ParseLigCaretListTable(ots::Font *font, const uint8_t *data, 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); + return Error("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); + return Error("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 Error("Bad caret value table structure %d in glyph %d", j, i); + } + if (caret_format == 3) { + uint16_t offset_device = 0; + if (!subtable.ReadU16(&offset_device)) { + return Error("Can't read device offset for caret value %d " + "in glyph %d", j, i); + } + uint16_t absolute_offset = lig_glyphs[i] + caret_value_offsets[j] + + offset_device; + if (offset_device == 0 || absolute_offset >= length) { + return Error("Bad device offset for caret value %d in glyph %d: %d", + j, i, offset_device); + } + if (!ots::ParseDeviceTable(GetFont(), data + absolute_offset, + length - absolute_offset)) { + return Error("Bad device table for caret value %d in glyph %d", + j, i, offset_device); + } } } } 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) { +bool OpenTypeGDEF::ParseMarkGlyphSetsDefTable(const uint8_t *data, size_t length) { 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"); + return Error("Can' read mark glyph table structure"); } if (format != 1) { - return OTS_FAILURE_MSG("bad mark glyph set table format: %u", format); + return Error("bad mark glyph set table format: %u", format); } const unsigned mark_sets_end = 2 * static_cast(mark_set_count) + 4; if (mark_sets_end > std::numeric_limits::max()) { - return OTS_FAILURE_MSG("Bad mark_set %d", mark_sets_end); + return Error("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); + return Error("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); + return Error("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); + if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, + length - offset_coverage, this->m_num_glyphs)) { + return Error("Failed to parse coverage table for mark set %d", i); } } - font->gdef->num_mark_glyph_sets = mark_set_count; + this->num_mark_glyph_sets = mark_set_count; return true; } -} // namespace +bool OpenTypeGDEF::Parse(const uint8_t *data, size_t length) { + OpenTypeMAXP *maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); -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"); + if (!maxp) { + return Error("No maxp table in font, needed by GDEF"); } - const uint16_t num_glyphs = font->maxp->num_glyphs; + this->m_num_glyphs = 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"); + uint16_t version_major = 0, version_minor = 0; + if (!table.ReadU16(&version_major) || + !table.ReadU16(&version_minor)) { + return Error("Incomplete table"); } - if (version < 0x00010000 || version == 0x00010001) { - return OTS_FAILURE_MSG("Bad version"); - } - - if (version >= 0x00010002) { - gdef->version_2 = true; + if (version_major != 1 || version_minor == 1) { // there is no v1.1 + return Error("Bad version"); } uint16_t offset_glyph_class_def = 0; @@ -264,110 +257,108 @@ bool ots_gdef_parse(Font *font, const uint8_t *data, size_t length) { !table.ReadU16(&offset_attach_list) || !table.ReadU16(&offset_lig_caret_list) || !table.ReadU16(&offset_mark_attach_class_def)) { - return OTS_FAILURE_MSG("Incomplete table"); + return Error("Incomplete table"); } uint16_t offset_mark_glyph_sets_def = 0; - if (gdef->version_2) { + if (version_minor >= 2) { if (!table.ReadU16(&offset_mark_glyph_sets_def)) { - return OTS_FAILURE_MSG("Incomplete table"); + return Error("Incomplete table"); + } + } + uint32_t item_var_store_offset = 0; + if (version_minor >= 3) { + if (!table.ReadU32(&item_var_store_offset)) { + return Error("Incomplete table"); } } unsigned gdef_header_end = 4 + 4 * 2; - if (gdef->version_2) + if (version_minor >= 2) gdef_header_end += 2; + if (version_minor >= 3) + gdef_header_end += 4; // 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"); + return Error("Invalid offset to glyph classes"); } - if (!ParseGlyphClassDefTable(font, data + offset_glyph_class_def, + if (!ots::ParseClassDefTable(GetFont(), data + offset_glyph_class_def, length - offset_glyph_class_def, - num_glyphs)) { - return OTS_FAILURE_MSG("Invalid glyph classes"); + this->m_num_glyphs, kMaxGlyphClassDefValue)) { + return Error("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"); + return Error("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 (!ParseAttachListTable(data + offset_attach_list, + length - offset_attach_list)) { + return Error("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"); + return Error("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 (!ParseLigCaretListTable(data + offset_lig_caret_list, + length - offset_lig_caret_list)) { + return Error("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"); + return Error("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"); + if (!ots::ParseClassDefTable(GetFont(), + data + offset_mark_attach_class_def, + length - offset_mark_attach_class_def, + this->m_num_glyphs, kMaxClassDefValue)) { + return Error("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"); + return Error("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"); + if (!ParseMarkGlyphSetsDefTable(data + offset_mark_glyph_sets_def, + length - offset_mark_glyph_sets_def)) { + return Error("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"); + if (item_var_store_offset) { + if (item_var_store_offset >= length || + item_var_store_offset < gdef_header_end) { + return Error("invalid offset to item variation store"); + } + if (!ParseItemVariationStore(GetFont(), data + item_var_store_offset, + length - item_var_store_offset)) { + return Error("Invalid item variation store"); + } } + this->m_data = data; + this->m_length = length; return true; } -void ots_gdef_reuse(Font *font, Font *other) { - font->gdef = other->gdef; - font->gdef_reused = true; -} +bool OpenTypeGDEF::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write table"); + } -void ots_gdef_free(Font *font) { - delete font->gdef; + return true; } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/gdef.h b/gfx/ots/src/gdef.h index f46f419c7..7c7cc0ce5 100644 --- a/gfx/ots/src/gdef.h +++ b/gfx/ots/src/gdef.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,25 +9,29 @@ 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), +class OpenTypeGDEF : public Table { + public: + explicit OpenTypeGDEF(Font *font, uint32_t tag) + : Table(font, tag, tag), num_mark_glyph_sets(0), - data(NULL), - length(0) { + m_data(NULL), + m_length(0), + m_num_glyphs(0) { } - bool version_2; - bool has_glyph_class_def; - bool has_mark_attachment_class_def; - bool has_mark_glyph_sets_def; + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + uint16_t num_mark_glyph_sets; - const uint8_t *data; - size_t length; + private: + bool ParseAttachListTable(const uint8_t *data, size_t length); + bool ParseLigCaretListTable(const uint8_t *data, size_t length); + bool ParseMarkGlyphSetsDefTable(const uint8_t *data, size_t length); + + const uint8_t *m_data; + size_t m_length; + uint16_t m_num_glyphs; }; } // namespace ots diff --git a/gfx/ots/src/glat.cc b/gfx/ots/src/glat.cc new file mode 100644 index 000000000..23f7dfd9a --- /dev/null +++ b/gfx/ots/src/glat.cc @@ -0,0 +1,459 @@ +// Copyright (c) 2009-2017 The OTS 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 "glat.h" + +#include "gloc.h" +#include "mozilla/Compression.h" +#include + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT_v1 +// ----------------------------------------------------------------------------- + +bool OpenTypeGLAT_v1::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + OpenTypeGLOC* gloc = static_cast( + GetFont()->GetTypedTable(OTS_TAG_GLOC)); + if (!gloc) { + return DropGraphite("Required Gloc table is missing"); + } + + if (!table.ReadU32(&this->version) || this->version >> 16 != 1) { + return DropGraphite("Failed to read version"); + } + + const std::vector& locations = gloc->GetLocations(); + if (locations.empty()) { + return DropGraphite("No locations from Gloc table"); + } + std::list unverified(locations.begin(), locations.end()); + while (table.remaining()) { + GlatEntry entry(this); + if (table.offset() > unverified.front()) { + return DropGraphite("Offset check failed for a GlatEntry"); + } + if (table.offset() == unverified.front()) { + unverified.pop_front(); + } + if (unverified.empty()) { + return DropGraphite("Expected more locations"); + } + if (!entry.ParsePart(table)) { + return DropGraphite("Failed to read a GlatEntry"); + } + this->entries.push_back(entry); + } + + if (unverified.size() != 1 || unverified.front() != table.offset()) { + return DropGraphite("%zu location(s) could not be verified", unverified.size()); + } + if (table.remaining()) { + return Warning("%zu bytes unparsed", table.remaining()); + } + return true; +} + +bool OpenTypeGLAT_v1::Serialize(OTSStream* out) { + if (!out->WriteU32(this->version) || + !SerializeParts(this->entries, out)) { + return Error("Failed to write table"); + } + return true; +} + +bool OpenTypeGLAT_v1::GlatEntry::ParsePart(Buffer& table) { + if (!table.ReadU8(&this->attNum)) { + return parent->Error("GlatEntry: Failed to read attNum"); + } + if (!table.ReadU8(&this->num)) { + return parent->Error("GlatEntry: Failed to read num"); + } + + //this->attributes.resize(this->num); + for (int i = 0; i < this->num; ++i) { + this->attributes.emplace_back(); + if (!table.ReadS16(&this->attributes[i])) { + return parent->Error("GlatEntry: Failed to read attribute %u", i); + } + } + return true; +} + +bool OpenTypeGLAT_v1::GlatEntry::SerializePart(OTSStream* out) const { + if (!out->WriteU8(this->attNum) || + !out->WriteU8(this->num) || + !SerializeParts(this->attributes, out)) { + return parent->Error("GlatEntry: Failed to write"); + } + return true; +} + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT_v2 +// ----------------------------------------------------------------------------- + +bool OpenTypeGLAT_v2::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + OpenTypeGLOC* gloc = static_cast( + GetFont()->GetTypedTable(OTS_TAG_GLOC)); + if (!gloc) { + return DropGraphite("Required Gloc table is missing"); + } + + if (!table.ReadU32(&this->version) || this->version >> 16 != 1) { + return DropGraphite("Failed to read version"); + } + + const std::vector& locations = gloc->GetLocations(); + if (locations.empty()) { + return DropGraphite("No locations from Gloc table"); + } + std::list unverified(locations.begin(), locations.end()); + while (table.remaining()) { + GlatEntry entry(this); + if (table.offset() > unverified.front()) { + return DropGraphite("Offset check failed for a GlatEntry"); + } + if (table.offset() == unverified.front()) { + unverified.pop_front(); + } + if (unverified.empty()) { + return DropGraphite("Expected more locations"); + } + if (!entry.ParsePart(table)) { + return DropGraphite("Failed to read a GlatEntry"); + } + this->entries.push_back(entry); + } + + if (unverified.size() != 1 || unverified.front() != table.offset()) { + return DropGraphite("%zu location(s) could not be verified", unverified.size()); + } + if (table.remaining()) { + return Warning("%zu bytes unparsed", table.remaining()); + } + return true; +} + +bool OpenTypeGLAT_v2::Serialize(OTSStream* out) { + if (!out->WriteU32(this->version) || + !SerializeParts(this->entries, out)) { + return Error("Failed to write table"); + } + return true; +} + +bool OpenTypeGLAT_v2::GlatEntry::ParsePart(Buffer& table) { + if (!table.ReadS16(&this->attNum)) { + return parent->Error("GlatEntry: Failed to read attNum"); + } + if (!table.ReadS16(&this->num) || this->num < 0) { + return parent->Error("GlatEntry: Failed to read valid num"); + } + + //this->attributes.resize(this->num); + for (int i = 0; i < this->num; ++i) { + this->attributes.emplace_back(); + if (!table.ReadS16(&this->attributes[i])) { + return parent->Error("GlatEntry: Failed to read attribute %u", i); + } + } + return true; +} + +bool OpenTypeGLAT_v2::GlatEntry::SerializePart(OTSStream* out) const { + if (!out->WriteS16(this->attNum) || + !out->WriteS16(this->num) || + !SerializeParts(this->attributes, out)) { + return parent->Error("GlatEntry: Failed to write"); + } + return true; +} + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT_v3 +// ----------------------------------------------------------------------------- + +bool OpenTypeGLAT_v3::Parse(const uint8_t* data, size_t length, + bool prevent_decompression) { + Buffer table(data, length); + OpenTypeGLOC* gloc = static_cast( + GetFont()->GetTypedTable(OTS_TAG_GLOC)); + if (!gloc) { + return DropGraphite("Required Gloc table is missing"); + } + + if (!table.ReadU32(&this->version) || this->version >> 16 != 3) { + return DropGraphite("Failed to read version"); + } + if (!table.ReadU32(&this->compHead)) { + return DropGraphite("Failed to read compression header"); + } + switch ((this->compHead & SCHEME) >> 27) { + case 0: // uncompressed + break; + case 1: { // lz4 + if (prevent_decompression) { + return DropGraphite("Illegal nested compression"); + } + size_t decompressed_size = this->compHead & FULL_SIZE; + if (decompressed_size < length) { + return DropGraphite("Decompressed size is less than compressed size"); + } + if (decompressed_size == 0) { + return DropGraphite("Decompressed size is set to 0"); + } + // decompressed table must be <= 30MB + if (decompressed_size > 30 * 1024 * 1024) { + return DropGraphite("Decompressed size exceeds 30MB: %gMB", + decompressed_size / (1024.0 * 1024.0)); + } + std::vector decompressed(decompressed_size); + size_t outputSize = 0; + bool ret = mozilla::Compression::LZ4::decompressPartial( + reinterpret_cast(data + table.offset()), + table.remaining(), // input buffer size (input size + padding) + reinterpret_cast(decompressed.data()), + decompressed.size(), // target output size + &outputSize); // return output size + if (!ret || outputSize != decompressed.size()) { + return DropGraphite("Decompression failed"); + } + return this->Parse(decompressed.data(), decompressed.size(), true); + } + default: + return DropGraphite("Unknown compression scheme"); + } + if (this->compHead & RESERVED) { + Warning("Nonzero reserved"); + } + + const std::vector& locations = gloc->GetLocations(); + if (locations.empty()) { + return DropGraphite("No locations from Gloc table"); + } + std::list unverified(locations.begin(), locations.end()); + //this->entries.resize(locations.size() - 1, this); + for (size_t i = 0; i < locations.size() - 1; ++i) { + this->entries.emplace_back(this); + if (table.offset() != unverified.front()) { + return DropGraphite("Offset check failed for a GlyphAttrs"); + } + unverified.pop_front(); + if (!this->entries[i].ParsePart(table, + unverified.front() - table.offset())) { + // unverified.front() is guaranteed to exist because of the number of + // iterations of this loop + return DropGraphite("Failed to read a GlyphAttrs"); + } + } + + if (unverified.size() != 1 || unverified.front() != table.offset()) { + return DropGraphite("%zu location(s) could not be verified", unverified.size()); + } + if (table.remaining()) { + return Warning("%zu bytes unparsed", table.remaining()); + } + return true; +} + +bool OpenTypeGLAT_v3::Serialize(OTSStream* out) { + if (!out->WriteU32(this->version) || + !out->WriteU32(this->compHead) || + !SerializeParts(this->entries, out)) { + return Error("Failed to write table"); + } + return true; +} + +bool OpenTypeGLAT_v3::GlyphAttrs::ParsePart(Buffer& table, const size_t size) { + size_t init_offset = table.offset(); + if (parent->compHead & OCTABOXES && !octabox.ParsePart(table)) { + // parent->flags & 0b1: octaboxes are present flag + return parent->Error("GlyphAttrs: Failed to read octabox"); + } + + while (table.offset() < init_offset + size) { + GlatEntry entry(parent); + if (!entry.ParsePart(table)) { + return parent->Error("GlyphAttrs: Failed to read a GlatEntry"); + } + this->entries.push_back(entry); + } + return true; +} + +bool OpenTypeGLAT_v3::GlyphAttrs::SerializePart(OTSStream* out) const { + if ((parent->compHead & OCTABOXES && !octabox.SerializePart(out)) || + !SerializeParts(this->entries, out)) { + return parent->Error("GlyphAttrs: Failed to write"); + } + return true; +} + +bool OpenTypeGLAT_v3::GlyphAttrs:: +OctaboxMetrics::ParsePart(Buffer& table) { + if (!table.ReadU16(&this->subbox_bitmap)) { + return parent->Error("OctaboxMetrics: Failed to read subbox_bitmap"); + } + if (!table.ReadU8(&this->diag_neg_min)) { + return parent->Error("OctaboxMetrics: Failed to read diag_neg_min"); + } + if (!table.ReadU8(&this->diag_neg_max) || + this->diag_neg_max < this->diag_neg_min) { + return parent->Error("OctaboxMetrics: Failed to read valid diag_neg_max"); + } + if (!table.ReadU8(&this->diag_pos_min)) { + return parent->Error("OctaboxMetrics: Failed to read diag_pos_min"); + } + if (!table.ReadU8(&this->diag_pos_max) || + this->diag_pos_max < this->diag_pos_min) { + return parent->Error("OctaboxMetrics: Failed to read valid diag_pos_max"); + } + + unsigned subboxes_len = 0; // count of 1's in this->subbox_bitmap + for (uint16_t i = this->subbox_bitmap; i; i >>= 1) { + if (i & 0b1) { + ++subboxes_len; + } + } + //this->subboxes.resize(subboxes_len, parent); + for (unsigned i = 0; i < subboxes_len; i++) { + this->subboxes.emplace_back(parent); + if (!this->subboxes[i].ParsePart(table)) { + return parent->Error("OctaboxMetrics: Failed to read subbox[%u]", i); + } + } + return true; +} + +bool OpenTypeGLAT_v3::GlyphAttrs:: +OctaboxMetrics::SerializePart(OTSStream* out) const { + if (!out->WriteU16(this->subbox_bitmap) || + !out->WriteU8(this->diag_neg_min) || + !out->WriteU8(this->diag_neg_max) || + !out->WriteU8(this->diag_pos_min) || + !out->WriteU8(this->diag_pos_max) || + !SerializeParts(this->subboxes, out)) { + return parent->Error("OctaboxMetrics: Failed to write"); + } + return true; +} + +bool OpenTypeGLAT_v3::GlyphAttrs::OctaboxMetrics:: +SubboxEntry::ParsePart(Buffer& table) { + if (!table.ReadU8(&this->left)) { + return parent->Error("SubboxEntry: Failed to read left"); + } + if (!table.ReadU8(&this->right) || this->right < this->left) { + return parent->Error("SubboxEntry: Failed to read valid right"); + } + if (!table.ReadU8(&this->bottom)) { + return parent->Error("SubboxEntry: Failed to read bottom"); + } + if (!table.ReadU8(&this->top) || this->top < this->bottom) { + return parent->Error("SubboxEntry: Failed to read valid top"); + } + if (!table.ReadU8(&this->diag_pos_min)) { + return parent->Error("SubboxEntry: Failed to read diag_pos_min"); + } + if (!table.ReadU8(&this->diag_pos_max) || + this->diag_pos_max < this->diag_pos_min) { + return parent->Error("SubboxEntry: Failed to read valid diag_pos_max"); + } + if (!table.ReadU8(&this->diag_neg_min)) { + return parent->Error("SubboxEntry: Failed to read diag_neg_min"); + } + if (!table.ReadU8(&this->diag_neg_max) || + this->diag_neg_max < this->diag_neg_min) { + return parent->Error("SubboxEntry: Failed to read valid diag_neg_max"); + } + return true; +} + +bool OpenTypeGLAT_v3::GlyphAttrs::OctaboxMetrics:: +SubboxEntry::SerializePart(OTSStream* out) const { + if (!out->WriteU8(this->left) || + !out->WriteU8(this->right) || + !out->WriteU8(this->bottom) || + !out->WriteU8(this->top) || + !out->WriteU8(this->diag_pos_min) || + !out->WriteU8(this->diag_pos_max) || + !out->WriteU8(this->diag_neg_min) || + !out->WriteU8(this->diag_neg_max)) { + return parent->Error("SubboxEntry: Failed to write"); + } + return true; +} + +bool OpenTypeGLAT_v3::GlyphAttrs:: +GlatEntry::ParsePart(Buffer& table) { + if (!table.ReadS16(&this->attNum)) { + return parent->Error("GlatEntry: Failed to read attNum"); + } + if (!table.ReadS16(&this->num) || this->num < 0) { + return parent->Error("GlatEntry: Failed to read valid num"); + } + + //this->attributes.resize(this->num); + for (int i = 0; i < this->num; ++i) { + this->attributes.emplace_back(); + if (!table.ReadS16(&this->attributes[i])) { + return parent->Error("GlatEntry: Failed to read attribute %u", i); + } + } + return true; +} + +bool OpenTypeGLAT_v3::GlyphAttrs:: +GlatEntry::SerializePart(OTSStream* out) const { + if (!out->WriteS16(this->attNum) || + !out->WriteS16(this->num) || + !SerializeParts(this->attributes, out)) { + return parent->Error("GlatEntry: Failed to write"); + } + return true; +} + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT +// ----------------------------------------------------------------------------- + +bool OpenTypeGLAT::Parse(const uint8_t* data, size_t length) { + if (GetFont()->dropped_graphite) { + return Drop("Skipping Graphite table"); + } + Buffer table(data, length); + uint32_t version; + if (!table.ReadU32(&version)) { + return DropGraphite("Failed to read version"); + } + switch (version >> 16) { + case 1: + this->handler = new OpenTypeGLAT_v1(this->font, this->tag); + break; + case 2: + this->handler = new OpenTypeGLAT_v2(this->font, this->tag); + break; + case 3: { + this->handler = new OpenTypeGLAT_v3(this->font, this->tag); + break; + } + default: + return DropGraphite("Unsupported table version: %u", version >> 16); + } + return this->handler->Parse(data, length); +} + +bool OpenTypeGLAT::Serialize(OTSStream* out) { + if (!this->handler) { + return Error("No Glat table parsed"); + } + return this->handler->Serialize(out); +} + +} // namespace ots diff --git a/gfx/ots/src/glat.h b/gfx/ots/src/glat.h new file mode 100644 index 000000000..04c9c1cce --- /dev/null +++ b/gfx/ots/src/glat.h @@ -0,0 +1,172 @@ +// Copyright (c) 2009-2017 The OTS 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_GLAT_H_ +#define OTS_GLAT_H_ + +#include + +#include "ots.h" +#include "graphite.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT_Basic Interface +// ----------------------------------------------------------------------------- + +class OpenTypeGLAT_Basic : public Table { + public: + explicit OpenTypeGLAT_Basic(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + virtual bool Parse(const uint8_t* data, size_t length) = 0; + virtual bool Serialize(OTSStream* out) = 0; +}; + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT_v1 +// ----------------------------------------------------------------------------- + +class OpenTypeGLAT_v1 : public OpenTypeGLAT_Basic { + public: + explicit OpenTypeGLAT_v1(Font* font, uint32_t tag) + : OpenTypeGLAT_Basic(font, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + struct GlatEntry : public TablePart { + explicit GlatEntry(OpenTypeGLAT_v1* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + uint8_t attNum; + uint8_t num; + std::vector attributes; + }; + uint32_t version; + std::vector entries; +}; + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT_v2 +// ----------------------------------------------------------------------------- + +class OpenTypeGLAT_v2 : public OpenTypeGLAT_Basic { + public: + explicit OpenTypeGLAT_v2(Font* font, uint32_t tag) + : OpenTypeGLAT_Basic(font, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + struct GlatEntry : public TablePart { + explicit GlatEntry(OpenTypeGLAT_v2* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + int16_t attNum; + int16_t num; + std::vector attributes; + }; + uint32_t version; + std::vector entries; +}; + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT_v3 +// ----------------------------------------------------------------------------- + +class OpenTypeGLAT_v3 : public OpenTypeGLAT_Basic { + public: + explicit OpenTypeGLAT_v3(Font* font, uint32_t tag) + : OpenTypeGLAT_Basic(font, tag) { } + + bool Parse(const uint8_t* data, size_t length) { + return this->Parse(data, length, false); + } + bool Serialize(OTSStream* out); + + private: + bool Parse(const uint8_t* data, size_t length, bool prevent_decompression); + struct GlyphAttrs : public TablePart { + explicit GlyphAttrs(OpenTypeGLAT_v3* parent) + : TablePart(parent), octabox(parent) { } + bool ParsePart(Buffer& table) { return false; } + bool ParsePart(Buffer& table, const size_t size); + bool SerializePart(OTSStream* out) const; + struct OctaboxMetrics : public TablePart { + explicit OctaboxMetrics(OpenTypeGLAT_v3* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + struct SubboxEntry : public TablePart { + explicit SubboxEntry(OpenTypeGLAT_v3* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + uint8_t left; + uint8_t right; + uint8_t bottom; + uint8_t top; + uint8_t diag_pos_min; + uint8_t diag_pos_max; + uint8_t diag_neg_min; + uint8_t diag_neg_max; + }; + uint16_t subbox_bitmap; + uint8_t diag_neg_min; + uint8_t diag_neg_max; + uint8_t diag_pos_min; + uint8_t diag_pos_max; + std::vector subboxes; + }; + struct GlatEntry : public TablePart { + explicit GlatEntry(OpenTypeGLAT_v3* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + int16_t attNum; + int16_t num; + std::vector attributes; + }; + OctaboxMetrics octabox; + std::vector entries; + }; + uint32_t version; + uint32_t compHead; // compression header + static const uint32_t SCHEME = 0xF8000000; + static const uint32_t FULL_SIZE = 0x07FFFFFF; + static const uint32_t RESERVED = 0x07FFFFFE; + static const uint32_t OCTABOXES = 0x00000001; + std::vector entries; +}; + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT +// ----------------------------------------------------------------------------- + +class OpenTypeGLAT : public Table { + public: + explicit OpenTypeGLAT(Font* font, uint32_t tag) + : Table(font, tag, tag), font(font), tag(tag) { } + OpenTypeGLAT(const OpenTypeGLAT& other) = delete; + OpenTypeGLAT& operator=(const OpenTypeGLAT& other) = delete; + ~OpenTypeGLAT() { delete handler; } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + Font* font; + uint32_t tag; + OpenTypeGLAT_Basic* handler = nullptr; +}; + +} // namespace ots + +#endif // OTS_GLAT_H_ diff --git a/gfx/ots/src/gloc.cc b/gfx/ots/src/gloc.cc new file mode 100644 index 000000000..9c5ee3bdf --- /dev/null +++ b/gfx/ots/src/gloc.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2009-2017 The OTS 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 "gloc.h" + +#include "name.h" + +namespace ots { + +bool OpenTypeGLOC::Parse(const uint8_t* data, size_t length) { + if (GetFont()->dropped_graphite) { + return Drop("Skipping Graphite table"); + } + Buffer table(data, length); + OpenTypeNAME* name = static_cast( + GetFont()->GetTypedTable(OTS_TAG_NAME)); + if (!name) { + return DropGraphite("Required name table is missing"); + } + + if (!table.ReadU32(&this->version)) { + return DropGraphite("Failed to read version"); + } + if (this->version >> 16 != 1) { + return DropGraphite("Unsupported table version: %u", this->version >> 16); + } + if (!table.ReadU16(&this->flags) || this->flags > 0b11) { + return DropGraphite("Failed to read valid flags"); + } + if (!table.ReadU16(&this->numAttribs)) { + return DropGraphite("Failed to read numAttribs"); + } + + if (this->flags & ATTRIB_IDS && this->numAttribs * sizeof(uint16_t) > + table.remaining()) { + return DropGraphite("Failed to calulate length of locations"); + } + size_t locations_len = (table.remaining() - + (this->flags & ATTRIB_IDS ? this->numAttribs * sizeof(uint16_t) : 0)) / + (this->flags & LONG_FORMAT ? sizeof(uint32_t) : sizeof(uint16_t)); + //this->locations.resize(locations_len); + if (this->flags & LONG_FORMAT) { + unsigned long last_location = 0; + for (size_t i = 0; i < locations_len; ++i) { + this->locations.emplace_back(); + uint32_t& location = this->locations[i]; + if (!table.ReadU32(&location) || location < last_location) { + return DropGraphite("Failed to read valid locations[%lu]", i); + } + last_location = location; + } + } else { // short (16-bit) offsets + unsigned last_location = 0; + for (size_t i = 0; i < locations_len; ++i) { + uint16_t location; + if (!table.ReadU16(&location) || location < last_location) { + return DropGraphite("Failed to read valid locations[%lu]", i); + } + last_location = location; + this->locations.push_back(static_cast(location)); + } + } + if (this->locations.empty()) { + return DropGraphite("No locations"); + } + + if (this->flags & ATTRIB_IDS) { // attribIds array present + //this->attribIds.resize(numAttribs); + for (unsigned i = 0; i < this->numAttribs; ++i) { + this->attribIds.emplace_back(); + if (!table.ReadU16(&this->attribIds[i]) || + !name->IsValidNameId(this->attribIds[i])) { + return DropGraphite("Failed to read valid attribIds[%u]", i); + } + } + } + + if (table.remaining()) { + return Warning("%zu bytes unparsed", table.remaining()); + } + return true; +} + +bool OpenTypeGLOC::Serialize(OTSStream* out) { + if (!out->WriteU32(this->version) || + !out->WriteU16(this->flags) || + !out->WriteU16(this->numAttribs) || + (this->flags & LONG_FORMAT ? !SerializeParts(this->locations, out) : + ![&] { + for (uint32_t location : this->locations) { + if (!out->WriteU16(static_cast(location))) { + return false; + } + } + return true; + }()) || + (this->flags & ATTRIB_IDS && !SerializeParts(this->attribIds, out))) { + return Error("Failed to write table"); + } + return true; +} + +const std::vector& OpenTypeGLOC::GetLocations() { + return this->locations; +} + +} // namespace ots diff --git a/gfx/ots/src/gloc.h b/gfx/ots/src/gloc.h new file mode 100644 index 000000000..60184ffb9 --- /dev/null +++ b/gfx/ots/src/gloc.h @@ -0,0 +1,36 @@ +// Copyright (c) 2009-2017 The OTS 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_GLOC_H_ +#define OTS_GLOC_H_ + +#include + +#include "ots.h" +#include "graphite.h" + +namespace ots { + +class OpenTypeGLOC : public Table { + public: + explicit OpenTypeGLOC(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + const std::vector& GetLocations(); + + private: + uint32_t version; + uint16_t flags; + static const uint16_t LONG_FORMAT = 0b1; + static const uint16_t ATTRIB_IDS = 0b10; + uint16_t numAttribs; + std::vector locations; + std::vector attribIds; +}; + +} // namespace ots + +#endif // OTS_GLOC_H_ diff --git a/gfx/ots/src/glyf.cc b/gfx/ots/src/glyf.cc index 311916dc0..0c19d6d7b 100644 --- a/gfx/ots/src/glyf.cc +++ b/gfx/ots/src/glyf.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -14,20 +14,15 @@ // glyf - Glyph Data // http://www.microsoft.com/typography/otspec/glyf.htm -#define TABLE_NAME "glyf" - -namespace { +namespace ots { -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) { +bool OpenTypeGLYF::ParseFlagsForSimpleGlyph(Buffer &glyph, + uint32_t num_flags, + uint32_t *flag_index, + uint32_t *coordinates_length) { uint8_t flag = 0; - if (!table->ReadU8(&flag)) { - return OTS_FAILURE_MSG("Can't read flag"); + if (!glyph.ReadU8(&flag)) { + return Error("Can't read flag"); } uint32_t delta = 0; @@ -43,140 +38,205 @@ bool ParseFlagsForSimpleGlyph(ots::Font *font, delta += 2; } + /* MS and Apple specs say this bit is reserved and must be set to zero, but + * Apple spec then contradicts itself and says it should be set on the first + * contour flag for simple glyphs with overlapping contours: + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6AATIntro.html + * (“Overlapping contours” section) */ + if (flag & (1u << 6) && *flag_index != 0) { + return Error("Bad glyph flag (%d), " + "bit 6 must be set to zero for flag %d", flag, *flag_index); + } + 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); + if (*flag_index + 1 >= num_flags) { + return Error("Count too high (%d + 1 >= %d)", *flag_index, num_flags); } uint8_t repeat = 0; - if (!table->ReadU8(&repeat)) { - return OTS_FAILURE_MSG("Can't read repeat value"); + if (!glyph.ReadU8(&repeat)) { + return Error("Can't read repeat value"); } if (repeat == 0) { - return OTS_FAILURE_MSG("Zero repeat"); + return Error("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); + *flag_index += repeat; + if (*flag_index >= num_flags) { + return Error("Count too high (%d >= %d)", *flag_index, 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); + if (flag & (1u << 7)) { // reserved flag + return Error("Bad glyph flag (%d), reserved bit 7 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); + *coordinates_length += delta; + if (glyph.length() < *coordinates_length) { + return Error("Glyph coordinates length bigger than glyph length (%d > %d)", + *coordinates_length, glyph.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; - +bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph, + int16_t num_contours) { // 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 (!glyph.ReadU16(&tmp_index)) { + return Error("Can't read contour index %d", i); } if (tmp_index == 0xffffu) { - return OTS_FAILURE_MSG("Bad contour index %d", i); + return Error("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); + return Error("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); + if (!glyph.ReadU16(&bytecode_length)) { + return Error("Can't read 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); + if (this->maxp->version_1 && + this->maxp->max_size_glyf_instructions < bytecode_length) { + this->maxp->max_size_glyf_instructions = bytecode_length; + Warning("Bytecode length is bigger than maxp.maxSizeOfInstructions %d: %d", + this->maxp->max_size_glyf_instructions, bytecode_length); } - glyf->iov.push_back(std::make_pair( - data + gly_offset, - static_cast(gly_header_length + bytecode_length))); - - if (!table->Skip(bytecode_length)) { - return OTS_FAILURE_MSG("Can't skip bytecode of length %d", bytecode_length); + if (!glyph.Skip(bytecode_length)) { + return Error("Can't read 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); + uint32_t coordinates_length = 0; + for (uint32_t i = 0; i < num_flags; ++i) { + if (!ParseFlagsForSimpleGlyph(glyph, num_flags, &i, &coordinates_length)) { + return Error("Failed to parse glyph flags %d", i); } } - 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 (!glyph.Skip(coordinates_length)) { + return Error("Glyph too short %d", glyph.length()); } - if (gly_length - (gly_header_length + bytecode_length + - flags_count_physical + xy_coordinates_length) > 3) { + if (glyph.remaining() > 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); + Warning("Extra bytes at end of the glyph: %d", glyph.remaining()); } - glyf->iov.push_back(std::make_pair( - data + gly_offset + gly_header_length + bytecode_length, - static_cast(flags_count_physical + xy_coordinates_length))); - - *new_size - = gly_header_length + flags_count_physical + xy_coordinates_length + bytecode_length; + this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset())); return true; } -} // namespace +#define ARG_1_AND_2_ARE_WORDS (1u << 0) +#define WE_HAVE_A_SCALE (1u << 3) +#define MORE_COMPONENTS (1u << 5) +#define WE_HAVE_AN_X_AND_Y_SCALE (1u << 6) +#define WE_HAVE_A_TWO_BY_TWO (1u << 7) +#define WE_HAVE_INSTRUCTIONS (1u << 8) + +bool OpenTypeGLYF::ParseCompositeGlyph(Buffer &glyph) { + uint16_t flags = 0; + uint16_t gid = 0; + do { + if (!glyph.ReadU16(&flags) || !glyph.ReadU16(&gid)) { + return Error("Can't read composite glyph flags or glyphIndex"); + } -namespace ots { + if (gid >= this->maxp->num_glyphs) { + return Error("Invalid glyph id used in composite glyph: %d", gid); + } + + if (flags & ARG_1_AND_2_ARE_WORDS) { + int16_t argument1; + int16_t argument2; + if (!glyph.ReadS16(&argument1) || !glyph.ReadS16(&argument2)) { + return Error("Can't read argument1 or argument2"); + } + } else { + uint8_t argument1; + uint8_t argument2; + if (!glyph.ReadU8(&argument1) || !glyph.ReadU8(&argument2)) { + return Error("Can't read argument1 or argument2"); + } + } + + if (flags & WE_HAVE_A_SCALE) { + int16_t scale; + if (!glyph.ReadS16(&scale)) { + return Error("Can't read scale"); + } + } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { + int16_t xscale; + int16_t yscale; + if (!glyph.ReadS16(&xscale) || !glyph.ReadS16(&yscale)) { + return Error("Can't read xscale or yscale"); + } + } else if (flags & WE_HAVE_A_TWO_BY_TWO) { + int16_t xscale; + int16_t scale01; + int16_t scale10; + int16_t yscale; + if (!glyph.ReadS16(&xscale) || + !glyph.ReadS16(&scale01) || + !glyph.ReadS16(&scale10) || + !glyph.ReadS16(&yscale)) { + return Error("Can't read transform"); + } + } + } while (flags & MORE_COMPONENTS); + + if (flags & WE_HAVE_INSTRUCTIONS) { + uint16_t bytecode_length; + if (!glyph.ReadU16(&bytecode_length)) { + return Error("Can't read instructions size"); + } -bool ots_glyf_parse(Font *font, const uint8_t *data, size_t length) { - Buffer table(data, length); + if (this->maxp->version_1 && + this->maxp->max_size_glyf_instructions < bytecode_length) { + this->maxp->max_size_glyf_instructions = bytecode_length; + Warning("Bytecode length is bigger than maxp.maxSizeOfInstructions " + "%d: %d", + this->maxp->max_size_glyf_instructions, bytecode_length); + } - if (!font->maxp || !font->loca || !font->head) { - return OTS_FAILURE_MSG("Missing maxp or loca or head table needed by glyf table"); + if (!glyph.Skip(bytecode_length)) { + return Error("Can't read bytecode of length %d", bytecode_length); + } } - OpenTypeGLYF *glyf = new OpenTypeGLYF; - font->glyf = glyf; + this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset())); - const unsigned num_glyphs = font->maxp->num_glyphs; - std::vector &offsets = font->loca->offsets; + return true; +} + +bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) { + OpenTypeMAXP *maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + OpenTypeLOCA *loca = static_cast( + GetFont()->GetTypedTable(OTS_TAG_LOCA)); + OpenTypeHEAD *head = static_cast( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); + if (!maxp || !loca || !head) { + return Error("Missing maxp or loca or head table needed by glyf table"); + } + + this->maxp = maxp; + + const unsigned num_glyphs = maxp->num_glyphs; + std::vector &offsets = loca->offsets; if (offsets.size() != num_glyphs + 1) { - return OTS_FAILURE_MSG("Invalide glyph offsets size %ld != %d", offsets.size(), num_glyphs + 1); + return Error("Invalide glyph offsets size %ld != %d", offsets.size(), num_glyphs + 1); } std::vector resulting_offsets(num_glyphs + 1); @@ -193,30 +253,31 @@ bool ots_glyf_parse(Font *font, const uint8_t *data, size_t length) { } if (gly_offset >= length) { - return OTS_FAILURE_MSG("Glyph %d offset %d too high %ld", i, gly_offset, length); + return Error("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); + return Error("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); + return Error("Glyph %d length %d too high", i, gly_length); } - table.set_offset(gly_offset); + Buffer glyph(data + gly_offset, gly_length); + 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 (!glyph.ReadS16(&num_contours) || + !glyph.ReadS16(&xmin) || + !glyph.ReadS16(&ymin) || + !glyph.ReadS16(&xmax) || + !glyph.ReadS16(&ymax)) { + return Error("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); + return Error("Bad number of contours %d in glyph %d", num_contours, i); } // workaround for fonts in http://www.princexml.com/fonts/ @@ -224,35 +285,36 @@ bool ots_glyf_parse(Font *font, const uint8_t *data, size_t length) { (xmax == -32767) && (ymin == 32767) && (ymax == -32767)) { - OTS_WARNING("bad xmin/xmax/ymin/ymax values"); + 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); + return Error("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); + if (num_contours == 0) { + // This is an empty glyph and shouldn’t have any glyph data, but if it + // does we will simply ignore it. + glyph.set_offset(0); + } else if (num_contours > 0) { + if (!ParseSimpleGlyph(glyph, num_contours)) { + return Error("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(gly_length))); - new_size = gly_length; + if (!ParseCompositeGlyph(glyph)) { + return Error("Failed to parse glyph %d", i); + } } + size_t new_size = glyph.offset(); 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( + this->iov.push_back(std::make_pair( reinterpret_cast("\x00\x00\x00\x00"), static_cast(padding))); new_size += padding; @@ -264,40 +326,32 @@ bool ots_glyf_parse(Font *font, const uint8_t *data, size_t length) { const uint16_t max16 = std::numeric_limits::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; + (head->index_to_loc_format != 1)) { + 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; + loca->offsets = resulting_offsets; - 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); - } + if (this->iov.empty()) { + // As a special case when all glyph in the font are empty, add a zero byte + // to the table, so that we don’t reject it down the way, and to make the + // table work on Windows as well. + // See https://github.com/khaledhosny/ots/issues/52 + static const uint8_t kZero = 0; + this->iov.push_back(std::make_pair(&kZero, 1)); } return true; } -void ots_glyf_reuse(Font *font, Font *other) { - font->glyf = other->glyf; - font->glyf_reused = true; -} +bool OpenTypeGLYF::Serialize(OTSStream *out) { + for (unsigned i = 0; i < this->iov.size(); ++i) { + if (!out->Write(this->iov[i].first, this->iov[i].second)) { + return Error("Falied to write glyph %d", i); + } + } -void ots_glyf_free(Font *font) { - delete font->glyf; + return true; } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/glyf.h b/gfx/ots/src/glyf.h index 9a8baf5ec..1da94e4b9 100644 --- a/gfx/ots/src/glyf.h +++ b/gfx/ots/src/glyf.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -12,8 +12,26 @@ #include "ots.h" namespace ots { +class OpenTypeMAXP; + +class OpenTypeGLYF : public Table { + public: + explicit OpenTypeGLYF(Font *font, uint32_t tag) + : Table(font, tag, tag), maxp(NULL) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + private: + bool ParseFlagsForSimpleGlyph(Buffer &glyph, + uint32_t num_flags, + uint32_t *flag_index, + uint32_t *coordinates_length); + bool ParseSimpleGlyph(Buffer &glyph, int16_t num_contours); + bool ParseCompositeGlyph(Buffer &glyph); + + OpenTypeMAXP* maxp; -struct OpenTypeGLYF { std::vector > iov; }; diff --git a/gfx/ots/src/gpos.cc b/gfx/ots/src/gpos.cc index 83d9ab053..034f9799d 100644 --- a/gfx/ots/src/gpos.cc +++ b/gfx/ots/src/gpos.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -30,12 +30,12 @@ enum GPOS_TYPE { GPOS_TYPE_RESERVED = 10 }; -// The size of gpos header. -const unsigned kGposHeaderSize = 10; +// The size of gpos header, version 1.0. +const unsigned kGposHeaderSize_1_0 = 10; +// The size of gpos header, version 1.1. +const unsigned kGposHeaderSize_1_1 = 14; // 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, @@ -76,9 +76,23 @@ const ots::LookupSubtableParser kGposLookupSubtableParser = { // Shared Tables: ValueRecord, Anchor Table, and MarkArray +size_t CalcValueRecordSize(const uint16_t value_format) { + size_t size = 0; + for (unsigned i = 0; i < 8; ++i) { + if ((value_format >> i) & 0x1) { + size += 2; + } + } + + return size; +} + bool ParseValueRecord(const ots::Font *font, - ots::Buffer* subtable, const uint8_t *data, - const size_t length, const uint16_t value_format) { + ots::Buffer* subtable, + const uint16_t value_format) { + const uint8_t *data = subtable->buffer(); + const size_t length = subtable->length(); + // Check existence of adjustment fields. for (unsigned i = 0; i < 4; ++i) { if ((value_format >> i) & 0x1) { @@ -207,6 +221,12 @@ bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data, const size_t length) { ots::Buffer subtable(data, length); + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + uint16_t format = 0; uint16_t offset_coverage = 0; uint16_t value_format = 0; @@ -218,7 +238,7 @@ bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data, if (format == 1) { // Format 1 exactly one value record. - if (!ParseValueRecord(font, &subtable, data, length, value_format)) { + if (!ParseValueRecord(font, &subtable, value_format)) { return OTS_FAILURE_MSG("Failed to parse format 1 single adjustment table"); } } else if (format == 2) { @@ -227,7 +247,7 @@ bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data, 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)) { + if (!ParseValueRecord(font, &subtable, value_format)) { return OTS_FAILURE_MSG("Failed to parse value record %d in format 2 single adjustment table", i); } } @@ -241,7 +261,7 @@ bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data, if (!ots::ParseCoverageTable(font, data + offset_coverage, length - offset_coverage, - font->maxp->num_glyphs)) { + maxp->num_glyphs)) { return OTS_FAILURE_MSG("Failed to parse coverage table in single adjustment table"); } @@ -268,10 +288,10 @@ bool ParsePairSetTable(const ots::Font *font, 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)) { + if (!ParseValueRecord(font, &subtable, 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)) { + if (!ParseValueRecord(font, &subtable, value_format2)) { return OTS_FAILURE_MSG("Failed to parse value record in format 2 pair set table"); } } @@ -341,34 +361,44 @@ bool ParsePairPosFormat2(const ots::Font *font, return OTS_FAILURE_MSG("Failed to read pair pos format 2 data"); } + size_t value_record1_size = CalcValueRecordSize(value_format1); + size_t value_record2_size = CalcValueRecordSize(value_format2); + size_t value_records_size = size_t(class1_count) * size_t(class2_count) * + (value_record1_size + value_record2_size); + + // Check the validity of class definition offsets. + if (offset_class_def1 < subtable.offset() + value_records_size || + offset_class_def2 < subtable.offset() + value_records_size || + offset_class_def1 >= length || offset_class_def2 >= length) { + return OTS_FAILURE_MSG("Bad ParsePairPosFormat2 class definition offsets %d or %d", offset_class_def1, offset_class_def2); + } + // 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); + if (value_record1_size || value_record2_size) { + for (unsigned i = 0; i < class1_count; ++i) { + // Check class 2 records. + for (unsigned j = 0; j < class2_count; ++j) { + if (value_format1 && value_record2_size && + !ParseValueRecord(font, &subtable, value_format1)) { + return OTS_FAILURE_MSG("Failed to parse value record 1 %d and %d", j, i); + } + if (value_format2 && value_record2_size && + !ParseValueRecord(font, &subtable, 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)) { + num_glyphs, ots::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)) { + num_glyphs, ots::kMaxClassDefValue)) { return OTS_FAILURE_MSG("Failed to parse class definition table 2"); } @@ -381,6 +411,12 @@ bool ParsePairAdjustment(const ots::Font *font, const uint8_t *data, const size_t length) { ots::Buffer subtable(data, length); + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + uint16_t format = 0; uint16_t offset_coverage = 0; uint16_t value_format1 = 0; @@ -394,12 +430,12 @@ bool ParsePairAdjustment(const ots::Font *font, const uint8_t *data, if (format == 1) { if (!ParsePairPosFormat1(font, data, length, value_format1, value_format2, - font->maxp->num_glyphs)) { + 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)) { + maxp->num_glyphs)) { return OTS_FAILURE_MSG("Failed to parse pair format 2"); } } else { @@ -411,7 +447,7 @@ bool ParsePairAdjustment(const ots::Font *font, const uint8_t *data, } if (!ots::ParseCoverageTable(font, data + offset_coverage, length - offset_coverage, - font->maxp->num_glyphs)) { + maxp->num_glyphs)) { return OTS_FAILURE_MSG("Failed to parse coverage table"); } @@ -424,6 +460,12 @@ bool ParseCursiveAttachment(const ots::Font *font, const uint8_t *data, const size_t length) { ots::Buffer subtable(data, length); + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + uint16_t format = 0; uint16_t offset_coverage = 0; uint16_t entry_exit_count = 0; @@ -478,7 +520,7 @@ bool ParseCursiveAttachment(const ots::Font *font, const uint8_t *data, } if (!ots::ParseCoverageTable(font, data + offset_coverage, length - offset_coverage, - font->maxp->num_glyphs)) { + maxp->num_glyphs)) { return OTS_FAILURE_MSG("Failed to parse coverage table in cursive attachment"); } @@ -552,6 +594,12 @@ bool ParseMarkToAttachmentSubtables(const ots::Font *font, const GPOS_TYPE type) { ots::Buffer subtable(data, length); + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + uint16_t format = 0; uint16_t offset_coverage1 = 0; uint16_t offset_coverage2 = 0; @@ -580,7 +628,7 @@ bool ParseMarkToAttachmentSubtables(const ots::Font *font, } if (!ots::ParseCoverageTable(font, data + offset_coverage1, length - offset_coverage1, - font->maxp->num_glyphs)) { + maxp->num_glyphs)) { return OTS_FAILURE_MSG("Failed to parse converge 1 table"); } if (offset_coverage2 < header_end || offset_coverage2 >= length) { @@ -588,7 +636,7 @@ bool ParseMarkToAttachmentSubtables(const ots::Font *font, } if (!ots::ParseCoverageTable(font, data + offset_coverage2, length - offset_coverage2, - font->maxp->num_glyphs)) { + maxp->num_glyphs)) { return OTS_FAILURE_MSG("Failed to parse coverage table 2"); } @@ -652,17 +700,37 @@ bool ParseMarkToMarkAttachment(const ots::Font *font, // 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); + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + ots::OpenTypeGPOS *gpos = static_cast( + font->GetTypedTable(OTS_TAG_GPOS)); + if (!gpos) { + return OTS_FAILURE_MSG("Internal error!"); + } + return ots::ParseContextSubtable(font, data, length, maxp->num_glyphs, + gpos->num_lookups); } // Lookup Type 8: // Chaining Contexual Positioning Subtable bool ParseChainedContextPositioning(const ots::Font *font, const uint8_t *data, const size_t length) { + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + ots::OpenTypeGPOS *gpos = static_cast( + font->GetTypedTable(OTS_TAG_GPOS)); + if (!gpos) { + return OTS_FAILURE_MSG("Internal error!"); + } return ots::ParseChainingContextSubtable(font, data, length, - font->maxp->num_glyphs, - font->gpos->num_lookups); + maxp->num_glyphs, + gpos->num_lookups); } // Lookup Type 9: @@ -677,139 +745,96 @@ bool ParseExtensionPositioning(const ots::Font *font, 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"); - } - +bool OpenTypeGPOS::Parse(const uint8_t *data, size_t length) { + Font *font = GetFont(); Buffer table(data, length); - OpenTypeGPOS *gpos = new OpenTypeGPOS; - font->gpos = gpos; - - uint32_t version = 0; + uint16_t version_major = 0, version_minor = 0; uint16_t offset_script_list = 0; uint16_t offset_feature_list = 0; uint16_t offset_lookup_list = 0; - if (!table.ReadU32(&version) || + uint32_t offset_feature_variations = 0; + if (!table.ReadU16(&version_major) || + !table.ReadU16(&version_minor) || !table.ReadU16(&offset_script_list) || !table.ReadU16(&offset_feature_list) || !table.ReadU16(&offset_lookup_list)) { - return OTS_FAILURE_MSG("Incomplete table"); + return Error("Incomplete table"); } - if (version != 0x00010000) { - return OTS_FAILURE_MSG("Bad version"); + if (version_major != 1 || version_minor > 1) { + return Error("Bad version"); + } + + if (version_minor > 0) { + if (!table.ReadU32(&offset_feature_variations)) { + return Error("Incomplete table"); + } } + const size_t header_size = + (version_minor == 0) ? kGposHeaderSize_1_0 : kGposHeaderSize_1_1; + 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 (offset_lookup_list < header_size || offset_lookup_list >= length) { + return Error("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"); + &this->num_lookups)) { + return Error("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 (offset_feature_list < header_size || offset_feature_list >= length) { + return Error("Bad feature list offset in table header"); } if (!ParseFeatureListTable(font, data + offset_feature_list, - length - offset_feature_list, gpos->num_lookups, + length - offset_feature_list, this->num_lookups, &num_features)) { - return OTS_FAILURE_MSG("Failed to parse feature list table"); + return Error("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 (offset_script_list < header_size || offset_script_list >= length) { + return Error("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"); + return Error("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; -} + if (offset_feature_variations) { + if (offset_feature_variations < header_size || offset_feature_variations >= length) { + return Error("Bad feature variations offset in table header"); + } -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"); + if (!ParseFeatureVariationsTable(font, data + offset_feature_variations, + length - offset_feature_variations, + this->num_lookups)) { + return Error("Failed to parse feature variations table"); + } } + this->m_data = data; + this->m_length = length; return true; } -void ots_gpos_reuse(Font *font, Font *other) { - font->gpos = other->gpos; - font->gpos_reused = true; -} +bool OpenTypeGPOS::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write GPOS table"); + } -void ots_gpos_free(Font *font) { - delete font->gpos; + return true; } } // namespace ots diff --git a/gfx/ots/src/gpos.h b/gfx/ots/src/gpos.h index 3a4034f6f..423c8ae83 100644 --- a/gfx/ots/src/gpos.h +++ b/gfx/ots/src/gpos.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,18 +9,24 @@ namespace ots { -struct OpenTypeGPOS { - OpenTypeGPOS() - : num_lookups(0), - data(NULL), - length(0) { +class OpenTypeGPOS : public Table { + public: + explicit OpenTypeGPOS(Font *font, uint32_t tag) + : Table(font, tag, tag), + num_lookups(0), + m_data(NULL), + m_length(0) { } + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + // Number of lookups in GPOS table uint16_t num_lookups; - const uint8_t *data; - size_t length; + private: + const uint8_t *m_data; + size_t m_length; }; } // namespace ots diff --git a/gfx/ots/src/graphite.h b/gfx/ots/src/graphite.h new file mode 100644 index 000000000..452cb26a8 --- /dev/null +++ b/gfx/ots/src/graphite.h @@ -0,0 +1,96 @@ +// Copyright (c) 2009-2017 The OTS 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_GRAPHITE_H_ +#define OTS_GRAPHITE_H_ + +#include +#include + +namespace ots { + +template +class TablePart { + public: + TablePart(ParentType* parent) : parent(parent) { } + virtual ~TablePart() { } + virtual bool ParsePart(Buffer& table) = 0; + virtual bool SerializePart(OTSStream* out) const = 0; + protected: + ParentType* parent; +}; + +template +bool SerializeParts(const std::vector& vec, OTSStream* out) { + for (const T& part : vec) { + if (!part.SerializePart(out)) { + return false; + } + } + return true; +} + +template +bool SerializeParts(const std::vector>& vec, OTSStream* out) { + for (const std::vector& part : vec) { + if (!SerializeParts(part, out)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector& vec, OTSStream* out) { + for (uint8_t part : vec) { + if (!out->WriteU8(part)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector& vec, OTSStream* out) { + for (uint16_t part : vec) { + if (!out->WriteU16(part)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector& vec, OTSStream* out) { + for (int16_t part : vec) { + if (!out->WriteS16(part)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector& vec, OTSStream* out) { + for (uint32_t part : vec) { + if (!out->WriteU32(part)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector& vec, OTSStream* out) { + for (int32_t part : vec) { + if (!out->WriteS32(part)) { + return false; + } + } + return true; +} + +template +size_t datasize(std::vector vec) { + return sizeof(T) * vec.size(); +} + +} // namespace ots + +#endif // OTS_GRAPHITE_H_ diff --git a/gfx/ots/src/gsub.cc b/gfx/ots/src/gsub.cc index 9baf2e88b..c90fb48f3 100644 --- a/gfx/ots/src/gsub.cc +++ b/gfx/ots/src/gsub.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -17,8 +17,10 @@ namespace { -// The GSUB header size -const size_t kGsubHeaderSize = 4 + 3 * 2; +// The GSUB header size for table version 1.0 +const size_t kGsubHeaderSize_1_0 = 4 + 3 * 2; +// GSUB header size v1.1 +const size_t kGsubHeaderSize_1_1 = 4 + 3 * 2 + 4; enum GSUB_TYPE { GSUB_TYPE_SINGLE = 1, @@ -82,7 +84,12 @@ bool ParseSingleSubstitution(const ots::Font *font, return OTS_FAILURE_MSG("Failed to read single subst table header"); } - const uint16_t num_glyphs = font->maxp->num_glyphs; + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; if (format == 1) { // Parse SingleSubstFormat1 int16_t delta_glyph_id = 0; @@ -170,7 +177,12 @@ bool ParseMutipleSubstitution(const ots::Font *font, return OTS_FAILURE_MSG("Bad multiple subst table format %d", format); } - const uint16_t num_glyphs = font->maxp->num_glyphs; + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; const unsigned sequence_end = static_cast(6) + sequence_count * 2; if (sequence_end > std::numeric_limits::max()) { @@ -245,7 +257,12 @@ bool ParseAlternateSubstitution(const ots::Font *font, return OTS_FAILURE_MSG("Bad alternate subst table format %d", format); } - const uint16_t num_glyphs = font->maxp->num_glyphs; + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; const unsigned alternate_set_end = static_cast(6) + alternate_set_count * 2; if (alternate_set_end > std::numeric_limits::max()) { @@ -362,7 +379,12 @@ bool ParseLigatureSubstitution(const ots::Font *font, return OTS_FAILURE_MSG("Bad ligature substitution table format %d", format); } - const uint16_t num_glyphs = font->maxp->num_glyphs; + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; const unsigned ligature_set_end = static_cast(6) + lig_set_count * 2; if (ligature_set_end > std::numeric_limits::max()) { @@ -398,8 +420,18 @@ bool ParseLigatureSubstitution(const ots::Font *font, // 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); + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + ots::OpenTypeGSUB *gsub = static_cast( + font->GetTypedTable(OTS_TAG_GSUB)); + if (!gsub) { + return OTS_FAILURE_MSG("Internal error!"); + } + return ots::ParseContextSubtable(font, data, length, maxp->num_glyphs, + gsub->num_lookups); } // Lookup Type 6: @@ -407,9 +439,19 @@ bool ParseContextSubstitution(const ots::Font *font, bool ParseChainingContextSubstitution(const ots::Font *font, const uint8_t *data, const size_t length) { + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + ots::OpenTypeGSUB *gsub = static_cast( + font->GetTypedTable(OTS_TAG_GSUB)); + if (!gsub) { + return OTS_FAILURE_MSG("Internal error!"); + } return ots::ParseChainingContextSubtable(font, data, length, - font->maxp->num_glyphs, - font->gsub->num_lookups); + maxp->num_glyphs, + gsub->num_lookups); } // Lookup Type 7: @@ -434,7 +476,12 @@ bool ParseReverseChainingContextSingleSubstitution( return OTS_FAILURE_MSG("Failed to read reverse chaining header"); } - const uint16_t num_glyphs = font->maxp->num_glyphs; + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; uint16_t backtrack_glyph_count = 0; if (!subtable.ReadU16(&backtrack_glyph_count)) { @@ -530,143 +577,97 @@ bool ParseReverseChainingContextSingleSubstitution( 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"); - } - +bool OpenTypeGSUB::Parse(const uint8_t *data, size_t length) { + // Parsing gsub table requires |maxp->num_glyphs| + Font *font = GetFont(); Buffer table(data, length); - OpenTypeGSUB *gsub = new OpenTypeGSUB; - font->gsub = gsub; - - uint32_t version = 0; + uint16_t version_major = 0, version_minor = 0; uint16_t offset_script_list = 0; uint16_t offset_feature_list = 0; uint16_t offset_lookup_list = 0; - if (!table.ReadU32(&version) || + uint32_t offset_feature_variations = 0; + if (!table.ReadU16(&version_major) || + !table.ReadU16(&version_minor) || !table.ReadU16(&offset_script_list) || !table.ReadU16(&offset_feature_list) || !table.ReadU16(&offset_lookup_list)) { - return OTS_FAILURE_MSG("Incomplete table"); + return Error("Incomplete table"); } - if (version != 0x00010000) { - return OTS_FAILURE_MSG("Bad version"); + if (version_major != 1 || version_minor > 1) { + return Error("Bad version"); } + if (version_minor > 0) { + if (!table.ReadU32(&offset_feature_variations)) { + return Error("Incomplete table"); + } + } + + const size_t header_size = + (version_minor == 0) ? kGsubHeaderSize_1_0 : kGsubHeaderSize_1_1; + 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 (offset_lookup_list < header_size || offset_lookup_list >= length) { + return Error("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"); + &this->num_lookups)) { + return Error("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 (offset_feature_list < header_size || offset_feature_list >= length) { + return Error("Bad feature list offset in table header"); } if (!ParseFeatureListTable(font, data + offset_feature_list, - length - offset_feature_list, gsub->num_lookups, + length - offset_feature_list, this->num_lookups, &num_features)) { - return OTS_FAILURE_MSG("Failed to parse feature list table"); + return Error("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 (offset_script_list < header_size || offset_script_list >= length) { + return Error("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"); + return Error("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; -} + if (offset_feature_variations) { + if (offset_feature_variations < header_size || offset_feature_variations >= length) { + return Error("Bad feature variations offset in table header"); + } -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"); + if (!ParseFeatureVariationsTable(font, data + offset_feature_variations, + length - offset_feature_variations, + this->num_lookups)) { + return Error("Failed to parse feature variations table"); + } } + this->m_data = data; + this->m_length = length; return true; } -void ots_gsub_reuse(Font *font, Font *other) { - font->gsub = other->gsub; - font->gsub_reused = true; -} +bool OpenTypeGSUB::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write GSUB table"); + } -void ots_gsub_free(Font *font) { - delete font->gsub; + return true; } } // namespace ots diff --git a/gfx/ots/src/gsub.h b/gfx/ots/src/gsub.h index f6f8cd3b1..76c990465 100644 --- a/gfx/ots/src/gsub.h +++ b/gfx/ots/src/gsub.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,18 +9,24 @@ namespace ots { -struct OpenTypeGSUB { - OpenTypeGSUB() - : num_lookups(0), - data(NULL), - length(0) { +class OpenTypeGSUB : public Table { + public: + explicit OpenTypeGSUB(Font *font, uint32_t tag) + : Table(font, tag, tag), + num_lookups(0), + m_data(NULL), + m_length(0) { } + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + // Number of lookups in GPSUB table uint16_t num_lookups; - const uint8_t *data; - size_t length; + //private: + const uint8_t *m_data; + size_t m_length; }; } // namespace ots diff --git a/gfx/ots/src/gvar.cc b/gfx/ots/src/gvar.cc new file mode 100644 index 000000000..324a0fc83 --- /dev/null +++ b/gfx/ots/src/gvar.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2018 The OTS 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 "gvar.h" + +#include "fvar.h" +#include "maxp.h" +#include "variations.h" + +#define TABLE_NAME "gvar" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeGVAR +// ----------------------------------------------------------------------------- + +static bool ParseSharedTuples(const Font* font, const uint8_t* data, size_t length, + size_t sharedTupleCount, size_t axisCount) { + Buffer subtable(data, length); + for (unsigned i = 0; i < sharedTupleCount; i++) { + for (unsigned j = 0; j < axisCount; j++) { + int16_t coordinate; + if (!subtable.ReadS16(&coordinate)) { + return OTS_FAILURE_MSG("Failed to read shared tuple coordinate"); + } + } + } + return true; +} + +static bool ParseGlyphVariationDataArray(const Font* font, const uint8_t* data, size_t length, + uint16_t flags, size_t glyphCount, size_t axisCount, + size_t sharedTupleCount, + const uint8_t* glyphVariationData, + size_t glyphVariationDataLength) { + Buffer subtable(data, length); + + bool glyphVariationDataOffsetsAreLong = (flags & 0x0001u); + uint32_t prevOffset = 0; + for (size_t i = 0; i < glyphCount + 1; i++) { + uint32_t offset; + if (glyphVariationDataOffsetsAreLong) { + if (!subtable.ReadU32(&offset)) { + return OTS_FAILURE_MSG("Failed to read GlyphVariationData offset"); + } + } else { + uint16_t halfOffset; + if (!subtable.ReadU16(&halfOffset)) { + return OTS_FAILURE_MSG("Failed to read GlyphVariationData offset"); + } + offset = halfOffset * 2; + } + + if (i > 0 && offset > prevOffset) { + if (prevOffset > glyphVariationDataLength) { + return OTS_FAILURE_MSG("Invalid GlyphVariationData offset"); + } + if (!ParseVariationData(font, glyphVariationData + prevOffset, + glyphVariationDataLength - prevOffset, + axisCount, sharedTupleCount)) { + return OTS_FAILURE_MSG("Failed to parse GlyphVariationData"); + } + } + prevOffset = offset; + } + + return true; +} + +bool OpenTypeGVAR::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + + uint16_t majorVersion; + uint16_t minorVersion; + uint16_t axisCount; + uint16_t sharedTupleCount; + uint32_t sharedTuplesOffset; + uint16_t glyphCount; + uint16_t flags; + uint32_t glyphVariationDataArrayOffset; + + if (!table.ReadU16(&majorVersion) || + !table.ReadU16(&minorVersion) || + !table.ReadU16(&axisCount) || + !table.ReadU16(&sharedTupleCount) || + !table.ReadU32(&sharedTuplesOffset) || + !table.ReadU16(&glyphCount) || + !table.ReadU16(&flags) || + !table.ReadU32(&glyphVariationDataArrayOffset)) { + return DropVariations("Failed to read table header"); + } + if (majorVersion != 1) { + return DropVariations("Unknown table version"); + } + + // check axisCount == fvar->axisCount + OpenTypeFVAR* fvar = static_cast( + GetFont()->GetTypedTable(OTS_TAG_FVAR)); + if (!fvar) { + return DropVariations("Required fvar table is missing"); + } + if (axisCount != fvar->AxisCount()) { + return DropVariations("Axis count mismatch"); + } + + // check glyphCount == maxp->num_glyphs + OpenTypeMAXP* maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return DropVariations("Required maxp table is missing"); + } + if (glyphCount != maxp->num_glyphs) { + return DropVariations("Glyph count mismatch"); + } + + if (sharedTupleCount > 0) { + if (sharedTuplesOffset < table.offset() || sharedTuplesOffset > length) { + return DropVariations("Invalid sharedTuplesOffset"); + } + if (!ParseSharedTuples(GetFont(), + data + sharedTuplesOffset, length - sharedTuplesOffset, + sharedTupleCount, axisCount)) { + return DropVariations("Failed to parse shared tuples"); + } + } + + if (glyphVariationDataArrayOffset) { + if (glyphVariationDataArrayOffset > length) { + return DropVariations("Invalid glyphVariationDataArrayOffset"); + } + if (!ParseGlyphVariationDataArray(GetFont(), + data + table.offset(), length - table.offset(), + flags, glyphCount, axisCount, sharedTupleCount, + data + glyphVariationDataArrayOffset, + length - glyphVariationDataArrayOffset)) { + return DropVariations("Failed to read glyph variation data array"); + } + } + + this->m_data = data; + this->m_length = length; + + return true; +} + +bool OpenTypeGVAR::Serialize(OTSStream* out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write gvar table"); + } + + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/gvar.h b/gfx/ots/src/gvar.h new file mode 100644 index 000000000..8a90c57a3 --- /dev/null +++ b/gfx/ots/src/gvar.h @@ -0,0 +1,31 @@ +// Copyright (c) 2018 The OTS 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_GVAR_H_ +#define OTS_GVAR_H_ + +#include "ots.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeGVAR Interface +// ----------------------------------------------------------------------------- + +class OpenTypeGVAR : public Table { + public: + explicit OpenTypeGVAR(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + const uint8_t *m_data; + size_t m_length; +}; + +} // namespace ots + +#endif // OTS_GVAR_H_ diff --git a/gfx/ots/src/hdmx.cc b/gfx/ots/src/hdmx.cc index f57b71f59..42ac9de8d 100644 --- a/gfx/ots/src/hdmx.cc +++ b/gfx/ots/src/hdmx.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,139 +9,112 @@ // 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) { +bool OpenTypeHDMX::Parse(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"); + OpenTypeMAXP *maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + OpenTypeHEAD *head = static_cast( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); + if (!head || !maxp) { + return Error("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; + if ((head->flags & 0x14) == 0) { + // http://www.microsoft.com/typography/otspec/recom.htm#hdmx + return Drop("the table should not be present when bit 2 and 4 of the " + "head->flags are not set"); } int16_t num_recs; - if (!table.ReadU16(&hdmx->version) || + if (!table.ReadU16(&this->version) || !table.ReadS16(&num_recs) || - !table.ReadS32(&hdmx->size_device_record)) { - return OTS_FAILURE_MSG("Failed to read hdmx header"); + !table.ReadS32(&this->size_device_record)) { + return Error("Failed to read table header"); } - if (hdmx->version != 0) { - DROP_THIS_TABLE("bad version: %u", hdmx->version); - return true; + if (this->version != 0) { + return Drop("Unsupported version: %u", this->version); } if (num_recs <= 0) { - DROP_THIS_TABLE("bad num_recs: %d", num_recs); - return true; + return Drop("Bad numRecords: %d", num_recs); } - 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; + const int32_t actual_size_device_record = maxp->num_glyphs + 2; + if (this->size_device_record < actual_size_device_record) { + return Drop("Bad sizeDeviceRecord: %d", this->size_device_record); } - 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); + this->pad_len = this->size_device_record - actual_size_device_record; + if (this->pad_len > 3) { + return Error("Bad DeviceRecord padding %d", this->pad_len); } uint8_t last_pixel_size = 0; - hdmx->records.reserve(num_recs); + this->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); + return Error("Failed to read DeviceRecord %d", i); } if ((i != 0) && (rec.pixel_size <= last_pixel_size)) { - DROP_THIS_TABLE("records are not sorted"); - return true; + return Drop("DeviceRecord's are not sorted"); } last_pixel_size = rec.pixel_size; - rec.widths.reserve(font->maxp->num_glyphs); - for (unsigned j = 0; j < font->maxp->num_glyphs; ++j) { + rec.widths.reserve(maxp->num_glyphs); + for (unsigned j = 0; j < 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); + return Error("Failed to read glyph width %d in DeviceRecord %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); + if ((this->pad_len > 0) && + !table.Skip(this->pad_len)) { + return Error("DeviceRecord %d should be padded by %d", i, this->pad_len); } - hdmx->records.push_back(rec); + this->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 OpenTypeHDMX::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; } -bool ots_hdmx_serialise(OTSStream *out, Font *font) { - OpenTypeHDMX * const hdmx = font->hdmx; - - const int16_t num_recs = static_cast(hdmx->records.size()); - if (hdmx->records.size() > +bool OpenTypeHDMX::Serialize(OTSStream *out) { + const int16_t num_recs = static_cast(this->records.size()); + if (this->records.size() > static_cast(std::numeric_limits::max()) || - !out->WriteU16(hdmx->version) || + !out->WriteU16(this->version) || !out->WriteS16(num_recs) || - !out->WriteS32(hdmx->size_device_record)) { - return OTS_FAILURE_MSG("Failed to write hdmx header"); + !out->WriteS32(this->size_device_record)) { + return Error("Failed to write table header"); } for (int16_t i = 0; i < num_recs; ++i) { - const OpenTypeHDMXDeviceRecord& rec = hdmx->records[i]; + const OpenTypeHDMXDeviceRecord& rec = this->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); + return Error("Failed to write DeviceRecord %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); + if ((this->pad_len > 0) && + !out->Write((const uint8_t *)"\x00\x00\x00", this->pad_len)) { + return Error("Failed to write padding of length %d", this->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 index 9ec212437..5d323dd7e 100644 --- a/gfx/ots/src/hdmx.h +++ b/gfx/ots/src/hdmx.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -17,7 +17,16 @@ struct OpenTypeHDMXDeviceRecord { std::vector widths; }; -struct OpenTypeHDMX { +class OpenTypeHDMX : public Table { + public: + explicit OpenTypeHDMX(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); + + private: uint16_t version; int32_t size_device_record; int32_t pad_len; diff --git a/gfx/ots/src/head.cc b/gfx/ots/src/head.cc index 229ea71f9..6088504c8 100644 --- a/gfx/ots/src/head.cc +++ b/gfx/ots/src/head.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,151 +9,124 @@ // 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) { +bool OpenTypeHEAD::Parse(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"); + !table.ReadU32(&this->revision)) { + return Error("Failed to read table header"); } if (version >> 16 != 1) { - return OTS_FAILURE_MSG("Bad head table version of %d", version); + return Error("Unsupported majorVersion: %d", version >> 16); } // Skip the checksum adjustment if (!table.Skip(4)) { - return OTS_FAILURE_MSG("Failed to read checksum"); + return Error("Failed to read checksum"); } uint32_t magic; if (!table.ReadU32(&magic) || magic != 0x5F0F3CF5) { - return OTS_FAILURE_MSG("Failed to read font magic number"); + return Error("Failed to read or incorrect magicNumber"); } - if (!table.ReadU16(&head->flags)) { - return OTS_FAILURE_MSG("Failed to read head flags"); + if (!table.ReadU16(&this->flags)) { + return Error("Failed to read 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"); - } + this->flags &= 0x381f; - // ppem must be in range - if (head->ppem < 16 || - head->ppem > 16384) { - return OTS_FAILURE_MSG("Bad ppm of %d", head->ppem); + if (!table.ReadU16(&this->upem)) { + return Error("Failed to read unitsPerEm"); } - // 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); + // upem must be in range + if (this->upem < 16 || + this->upem > 16384) { + return Error("unitsPerEm on in the range [16, 16384]: %d", this->upem); } -#endif - if (!table.ReadR64(&head->created) || - !table.ReadR64(&head->modified)) { - return OTS_FAILURE_MSG("Can't read font dates"); + if (!table.ReadR64(&this->created) || + !table.ReadR64(&this->modified)) { + return Error("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 (!table.ReadS16(&this->xmin) || + !table.ReadS16(&this->ymin) || + !table.ReadS16(&this->xmax) || + !table.ReadS16(&this->ymax)) { + return Error("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 (this->xmin > this->xmax) { + return Error("Bad x dimension in the font bounding box (%d, %d)", + this->xmin, this->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 (this->ymin > this->ymax) { + return Error("Bad y dimension in the font bounding box (%d, %d)", + this->ymin, this->ymax); } - if (!table.ReadU16(&head->mac_style)) { - return OTS_FAILURE_MSG("Failed to read font style"); + if (!table.ReadU16(&this->mac_style)) { + return Error("Failed to read macStyle"); } // We allow bits 0..6 - head->mac_style &= 0x7f; + this->mac_style &= 0x7f; - if (!table.ReadU16(&head->min_ppem)) { - return OTS_FAILURE_MSG("Failed to read font minimum ppm"); + if (!table.ReadU16(&this->min_ppem)) { + return Error("Failed to read lowestRecPPEM"); } // We don't care about the font direction hint if (!table.Skip(2)) { - return OTS_FAILURE_MSG("Failed to skip font direction hint"); + return Error("Failed to read fontDirectionHint"); } - if (!table.ReadS16(&head->index_to_loc_format)) { - return OTS_FAILURE_MSG("Failed to read index to loc format"); + if (!table.ReadS16(&this->index_to_loc_format)) { + return Error("Failed to read indexToLocFormat"); } - 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); + if (this->index_to_loc_format < 0 || + this->index_to_loc_format > 1) { + return Error("Bad indexToLocFormat %d", this->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 Error("Failed to read or bad glyphDataFormat"); } 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; +bool OpenTypeHEAD::Serialize(OTSStream *out) { if (!out->WriteU32(0x00010000) || - !out->WriteU32(head->revision) || + !out->WriteU32(this->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->WriteU16(this->flags) || + !out->WriteU16(this->upem) || + !out->WriteR64(this->created) || + !out->WriteR64(this->modified) || + !out->WriteS16(this->xmin) || + !out->WriteS16(this->ymin) || + !out->WriteS16(this->xmax) || + !out->WriteS16(this->ymax) || + !out->WriteU16(this->mac_style) || + !out->WriteU16(this->min_ppem) || !out->WriteS16(2) || - !out->WriteS16(head->index_to_loc_format) || + !out->WriteS16(this->index_to_loc_format) || !out->WriteS16(0)) { - return OTS_FAILURE_MSG("Failed to write head table"); + return Error("Failed to write 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 index 5967c4b89..a040fcf24 100644 --- a/gfx/ots/src/head.h +++ b/gfx/ots/src/head.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,10 +9,17 @@ namespace ots { -struct OpenTypeHEAD { +class OpenTypeHEAD : public Table { + public: + explicit OpenTypeHEAD(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + uint32_t revision; uint16_t flags; - uint16_t ppem; + uint16_t upem; uint64_t created; uint64_t modified; diff --git a/gfx/ots/src/hhea.cc b/gfx/ots/src/hhea.cc index 624287280..d024aaac4 100644 --- a/gfx/ots/src/hhea.cc +++ b/gfx/ots/src/hhea.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,49 +10,23 @@ // 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) { +bool OpenTypeHHEA::Parse(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 (!table.ReadU32(&this->version)) { + return Error("Failed to read table version"); } - - if (!ParseMetricsHeader(font, &table, &hhea->header)) { - return OTS_FAILURE_MSG("Failed to parse horizontal metrics"); + if (this->version >> 16 != 1) { + return Error("Unsupported majorVersion: %d", this->version >> 16); } - return true; + return OpenTypeMetricsHeader::Parse(data, length); } -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; +bool OpenTypeHHEA::Serialize(OTSStream *out) { + return OpenTypeMetricsHeader::Serialize(out); } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/hhea.h b/gfx/ots/src/hhea.h index bdea9aa0d..0405bd9d1 100644 --- a/gfx/ots/src/hhea.h +++ b/gfx/ots/src/hhea.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,8 +10,13 @@ namespace ots { -struct OpenTypeHHEA { - OpenTypeMetricsHeader header; +class OpenTypeHHEA : public OpenTypeMetricsHeader { + public: + explicit OpenTypeHHEA(Font *font, uint32_t tag) + : OpenTypeMetricsHeader(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); }; } // namespace ots diff --git a/gfx/ots/src/hmtx.cc b/gfx/ots/src/hmtx.cc index 667d1fe6f..2ef7771fd 100644 --- a/gfx/ots/src/hmtx.cc +++ b/gfx/ots/src/hmtx.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,47 +10,14 @@ // 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 OpenTypeHMTX::Parse(const uint8_t *data, size_t length) { + return OpenTypeMetricsTable::Parse(data, length); } -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; +bool OpenTypeHMTX::Serialize(OTSStream *out) { + return OpenTypeMetricsTable::Serialize(out); } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/hmtx.h b/gfx/ots/src/hmtx.h index 435949c5e..dbea9c80c 100644 --- a/gfx/ots/src/hmtx.h +++ b/gfx/ots/src/hmtx.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -6,12 +6,18 @@ #define OTS_HMTX_H_ #include "metrics.h" +#include "hhea.h" #include "ots.h" namespace ots { -struct OpenTypeHMTX { - OpenTypeMetricsTable metrics; +class OpenTypeHMTX : public OpenTypeMetricsTable { + public: + explicit OpenTypeHMTX(Font *font, uint32_t tag) + : OpenTypeMetricsTable(font, tag, tag, OTS_TAG_HHEA) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); }; } // namespace ots diff --git a/gfx/ots/src/hvar.cc b/gfx/ots/src/hvar.cc new file mode 100644 index 000000000..2bcea8680 --- /dev/null +++ b/gfx/ots/src/hvar.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2018 The OTS 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 "hvar.h" + +#include "variations.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeHVAR +// ----------------------------------------------------------------------------- + +bool OpenTypeHVAR::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + + uint16_t majorVersion; + uint16_t minorVersion; + uint32_t itemVariationStoreOffset; + uint32_t advanceWidthMappingOffset; + uint32_t lsbMappingOffset; + uint32_t rsbMappingOffset; + + if (!table.ReadU16(&majorVersion) || + !table.ReadU16(&minorVersion) || + !table.ReadU32(&itemVariationStoreOffset) || + !table.ReadU32(&advanceWidthMappingOffset) || + !table.ReadU32(&lsbMappingOffset) || + !table.ReadU32(&rsbMappingOffset)) { + return DropVariations("Failed to read table header"); + } + + if (majorVersion != 1) { + return DropVariations("Unknown table version"); + } + + if (itemVariationStoreOffset > length || + advanceWidthMappingOffset > length || + lsbMappingOffset > length || + rsbMappingOffset > length) { + return DropVariations("Invalid subtable offset"); + } + + if (!ParseItemVariationStore(GetFont(), data + itemVariationStoreOffset, + length - itemVariationStoreOffset)) { + return DropVariations("Failed to parse item variation store"); + } + + if (advanceWidthMappingOffset) { + if (!ParseDeltaSetIndexMap(GetFont(), data + advanceWidthMappingOffset, + length - advanceWidthMappingOffset)) { + return DropVariations("Failed to parse advance width mappings"); + } + } + + if (lsbMappingOffset) { + if (!ParseDeltaSetIndexMap(GetFont(), data + lsbMappingOffset, + length - lsbMappingOffset)) { + return DropVariations("Failed to parse LSB mappings"); + } + } + + if (rsbMappingOffset) { + if (!ParseDeltaSetIndexMap(GetFont(), data + rsbMappingOffset, + length - rsbMappingOffset)) { + return DropVariations("Failed to parse RSB mappings"); + } + } + + this->m_data = data; + this->m_length = length; + + return true; +} + +bool OpenTypeHVAR::Serialize(OTSStream* out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write HVAR table"); + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/hvar.h b/gfx/ots/src/hvar.h new file mode 100644 index 000000000..5bfd2e4ea --- /dev/null +++ b/gfx/ots/src/hvar.h @@ -0,0 +1,31 @@ +// Copyright (c) 2018 The OTS 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_HVAR_H_ +#define OTS_HVAR_H_ + +#include "ots.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeHVAR Interface +// ----------------------------------------------------------------------------- + +class OpenTypeHVAR : public Table { + public: + explicit OpenTypeHVAR(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + const uint8_t *m_data; + size_t m_length; +}; + +} // namespace ots + +#endif // OTS_HVAR_H_ diff --git a/gfx/ots/src/kern.cc b/gfx/ots/src/kern.cc index d4ae8fcc4..ec41dfb9c 100644 --- a/gfx/ots/src/kern.cc +++ b/gfx/ots/src/kern.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,75 +7,61 @@ // 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) { +bool OpenTypeKERN::Parse(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) || + if (!table.ReadU16(&this->version) || !table.ReadU16(&num_tables)) { - return OTS_FAILURE_MSG("Failed to read kern header"); + return Error("Failed to read table header"); } - if (kern->version > 0) { - DROP_THIS_TABLE("bad table version"); - return true; + if (this->version > 0) { + return Drop("Unsupported table version: %d", this->version); } if (num_tables == 0) { - DROP_THIS_TABLE("num_tables is zero"); - return true; + return Drop("nTables is zero"); } - kern->subtables.reserve(num_tables); + this->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); + return Error("Failed to read subtable %d header", i); } if (subtable.version > 0) { - OTS_WARNING("Bad subtable version: %d", subtable.version); + Warning("Ignoring subtable %d with unsupported version: %d", + i, 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); + return Error("Bad subtable %d offset %ld", i, current_offset); } if (!table.ReadU16(&subtable.coverage)) { - return OTS_FAILURE_MSG("Cailed to read kern subtable %d coverage", i); + return Error("Failed to read subtable %d coverage", i); } if (!(subtable.coverage & 0x1)) { - OTS_WARNING( + 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; + return Drop("Reserved fields should be zero"); } const uint32_t format = (subtable.coverage & 0xFF00) >> 8; if (format != 0) { - OTS_WARNING("Format %d is not supported.", format); + Warning("Ignoring subtable %d with unsupported format: %d", i, format); continue; } @@ -85,12 +71,11 @@ bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) { !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); + return Error("Failed to read subtable %d format 0 fields", i); } if (!num_pairs) { - DROP_THIS_TABLE("Zero length subtable is found"); - return true; + return Drop("Zero length subtable is found"); } // Sanity checks for search_range, entry_selector, and range_shift. See the @@ -98,8 +83,7 @@ bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) { 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; + return Drop("Too large subtable"); } unsigned max_pow2 = 0; while (1u << (max_pow2 + 1) <= num_pairs) { @@ -107,16 +91,16 @@ bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) { } const uint16_t expected_search_range = (1u << max_pow2) * kFormat0PairSize; if (subtable.search_range != expected_search_range) { - OTS_WARNING("bad search range"); + 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); + return Error("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"); + Warning("bad range shift"); subtable.range_shift = expected_range_shift; } @@ -128,64 +112,55 @@ bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) { 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); + return Error("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; + return Drop("Kerning pairs are not sorted"); } last_pair = current_pair; subtable.pairs.push_back(kerning_pair); } - kern->subtables.push_back(subtable); + this->subtables.push_back(subtable); } - if (!kern->subtables.size()) { - DROP_THIS_TABLE("All subtables are removed"); - return true; + if (!this->subtables.size()) { + return Drop("All subtables were removed"); } 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(kern->subtables.size()); - if (num_subtables != kern->subtables.size() || - !out->WriteU16(kern->version) || +bool OpenTypeKERN::Serialize(OTSStream *out) { + const uint16_t num_subtables = static_cast(this->subtables.size()); + if (num_subtables != this->subtables.size() || + !out->WriteU16(this->version) || !out->WriteU16(num_subtables)) { - return OTS_FAILURE_MSG("Can't write kern table header"); + return Error("Failed to write kern table header"); } for (uint16_t i = 0; i < num_subtables; ++i) { - const size_t length = 14 + (6 * kern->subtables[i].pairs.size()); + const size_t length = 14 + (6 * this->subtables[i].pairs.size()); if (length > std::numeric_limits::max() || - !out->WriteU16(kern->subtables[i].version) || + !out->WriteU16(this->subtables[i].version) || !out->WriteU16(static_cast(length)) || - !out->WriteU16(kern->subtables[i].coverage) || + !out->WriteU16(this->subtables[i].coverage) || !out->WriteU16( - static_cast(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); + static_cast(this->subtables[i].pairs.size())) || + !out->WriteU16(this->subtables[i].search_range) || + !out->WriteU16(this->subtables[i].entry_selector) || + !out->WriteU16(this->subtables[i].range_shift)) { + return Error("Failed to write kern subtable %d", i); + } + for (unsigned j = 0; j < this->subtables[i].pairs.size(); ++j) { + if (!out->WriteU16(this->subtables[i].pairs[j].left) || + !out->WriteU16(this->subtables[i].pairs[j].right) || + !out->WriteS16(this->subtables[i].pairs[j].value)) { + return Error("Failed to write kern pair %d for subtable %d", j, i); } } } @@ -193,16 +168,10 @@ bool ots_kern_serialise(OTSStream *out, Font *font) { 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; +bool OpenTypeKERN::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; } } // namespace ots - -#undef TABLE_NAME -#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/kern.h b/gfx/ots/src/kern.h index 9350ef7f8..38f056851 100644 --- a/gfx/ots/src/kern.h +++ b/gfx/ots/src/kern.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -30,7 +30,16 @@ struct OpenTypeKERNFormat0 { // WebFonts unlikely use it. I've checked thousands of proprietary fonts and // free fonts, and found no font uses the format. -struct OpenTypeKERN { +class OpenTypeKERN : public Table { + public: + explicit OpenTypeKERN(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); + + private: uint16_t version; std::vector subtables; }; diff --git a/gfx/ots/src/layout.cc b/gfx/ots/src/layout.cc index 1d87b53d4..8e99f573d 100644 --- a/gfx/ots/src/layout.cc +++ b/gfx/ots/src/layout.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,6 +7,7 @@ #include #include +#include "fvar.h" #include "gdef.h" // OpenType Layout Common Table Formats @@ -22,14 +23,11 @@ const uint32_t kScriptTableTagDflt = 0x44464c54; 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; +// In variation fonts, Device Tables are replaced by VariationIndex tables, +// indicated by this flag in the deltaFormat field. +const uint16_t kVariationIndex = 0x8000; struct ScriptRecord { uint32_t tag; @@ -94,15 +92,12 @@ bool ParseScriptTable(const ots::Font *font, } // The spec requires a script table for 'DFLT' tag must contain non-NULL - // |offset_default_lang_sys| and |lang_sys_count| == 0 + // |offset_default_lang_sys|. // 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 = @@ -197,27 +192,7 @@ bool ParseLookupTable(ots::Font *font, const uint8_t *data, 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; - } + bool use_mark_filtering_set = lookup_flag & kUseMarkFilteringSetBit; std::vector subtables; subtables.reserve(subtable_count); @@ -248,8 +223,12 @@ bool ParseLookupTable(ots::Font *font, const uint8_t *data, 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) { + + ots::OpenTypeGDEF *gdef = static_cast( + font->GetTypedTable(OTS_TAG_GDEF)); + + if (gdef && (gdef->num_mark_glyph_sets == 0 || + mark_filtering_set >= gdef->num_mark_glyph_sets)) { return OTS_FAILURE_MSG("Bad mark filtering set %d", mark_filtering_set); } } @@ -311,15 +290,15 @@ bool ParseClassDefFormat2(const ots::Font *font, // Skip format field. if (!subtable.Skip(2)) { - return OTS_FAILURE_MSG("Failed to skip format of class defintion header"); + return OTS_FAILURE_MSG("Failed to read class definition format"); } uint16_t range_count = 0; if (!subtable.ReadU16(&range_count)) { - return OTS_FAILURE_MSG("Failed to read range count in class definition"); + return OTS_FAILURE_MSG("Failed to read classRangeCount"); } if (range_count > num_glyphs) { - return OTS_FAILURE_MSG("bad range count: %u", range_count); + return OTS_FAILURE_MSG("classRangeCount > glyph count: %u > %u", range_count, num_glyphs); } uint16_t last_end = 0; @@ -330,13 +309,16 @@ bool ParseClassDefFormat2(const ots::Font *font, if (!subtable.ReadU16(&start) || !subtable.ReadU16(&end) || !subtable.ReadU16(&class_value)) { - return OTS_FAILURE_MSG("Failed to read class definition reange %d", i); + return OTS_FAILURE_MSG("Failed to read ClassRangeRecord %d", i); + } + if (start > end) { + return OTS_FAILURE_MSG("ClassRangeRecord %d, start > end: %u > %u", i, start, end); } - if (start > end || (last_end && start <= last_end)) { - return OTS_FAILURE_MSG("glyph range is overlapping.in range %d", i); + if (last_end && start <= last_end) { + return OTS_FAILURE_MSG("ClassRangeRecord %d start overlaps with end of the previous one: %u <= %u", i, start, last_end); } if (class_value > num_classes) { - return OTS_FAILURE_MSG("bad class value: %u", class_value); + return OTS_FAILURE_MSG("ClassRangeRecord %d class > number of classes: %u > %u", i, class_value, num_classes); } last_end = end; } @@ -661,7 +643,7 @@ bool ParseContextFormat2(const ots::Font *font, } if (!ots::ParseClassDefTable(font, data + offset_class_def, length - offset_class_def, - num_glyphs, kMaxClassDefValue)) { + num_glyphs, ots::kMaxClassDefValue)) { return OTS_FAILURE_MSG("Failed to parse class definition table in context format 2"); } @@ -1014,7 +996,7 @@ bool ParseChainContextFormat2(const ots::Font *font, } if (!ots::ParseClassDefTable(font, data + offset_backtrack_class_def, length - offset_backtrack_class_def, - num_glyphs, kMaxClassDefValue)) { + num_glyphs, ots::kMaxClassDefValue)) { return OTS_FAILURE_MSG("Failed to parse backtrack class defn table in chain context format 2"); } } @@ -1025,7 +1007,7 @@ bool ParseChainContextFormat2(const ots::Font *font, } if (!ots::ParseClassDefTable(font, data + offset_input_class_def, length - offset_input_class_def, - num_glyphs, kMaxClassDefValue)) { + num_glyphs, ots::kMaxClassDefValue)) { return OTS_FAILURE_MSG("Failed to parse input class defn in chain context format 2"); } @@ -1036,7 +1018,7 @@ bool ParseChainContextFormat2(const ots::Font *font, } if (!ots::ParseClassDefTable(font, data + offset_lookahead_class_def, length - offset_lookahead_class_def, - num_glyphs, kMaxClassDefValue)) { + num_glyphs, ots::kMaxClassDefValue)) { return OTS_FAILURE_MSG("Failed to parse lookahead class defn in chain context format 2"); } } @@ -1400,11 +1382,17 @@ bool ParseDeviceTable(const ots::Font *font, !subtable.ReadU16(&delta_format)) { return OTS_FAILURE_MSG("Failed to read device table header"); } + if (delta_format == kVariationIndex) { + // start_size and end_size are replaced by deltaSetOuterIndex + // and deltaSetInnerIndex respectively, but we don't attempt to + // check them here, so nothing more to do. + return true; + } if (start_size > end_size) { - return OTS_FAILURE_MSG("bad size range: %u > %u", start_size, end_size); + return OTS_FAILURE_MSG("Bad device table 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); + return OTS_FAILURE_MSG("Bad device table delta format: 0x%x", delta_format); } // The number of delta values per uint16. The device table should contain // at least |num_units| * 2 bytes compressed data. @@ -1516,6 +1504,173 @@ bool ParseExtensionSubtable(const Font *font, return true; } +bool ParseConditionTable(const Font *font, + const uint8_t *data, const size_t length, + const uint16_t axis_count) { + Buffer subtable(data, length); + + uint16_t format = 0; + if (!subtable.ReadU16(&format)) { + return OTS_FAILURE_MSG("Failed to read condition table format"); + } + + if (format != 1) { + // An unknown format is not an error, but should be ignored per spec. + return true; + } + + uint16_t axis_index = 0; + int16_t filter_range_min_value = 0; + int16_t filter_range_max_value = 0; + if (!subtable.ReadU16(&axis_index) || + !subtable.ReadS16(&filter_range_min_value) || + !subtable.ReadS16(&filter_range_max_value)) { + return OTS_FAILURE_MSG("Failed to read condition table (format 1)"); + } + + if (axis_index >= axis_count) { + return OTS_FAILURE_MSG("Axis index out of range in condition"); + } + + // Check min/max values are within range -1.0 .. 1.0 and properly ordered + if (filter_range_min_value < -0x4000 || // -1.0 in F2DOT14 format + filter_range_max_value > 0x4000 || // +1.0 in F2DOT14 format + filter_range_min_value > filter_range_max_value) { + return OTS_FAILURE_MSG("Invalid filter range in condition"); + } + + return true; +} + +bool ParseConditionSetTable(const Font *font, + const uint8_t *data, const size_t length, + const uint16_t axis_count) { + Buffer subtable(data, length); + + uint16_t condition_count = 0; + if (!subtable.ReadU16(&condition_count)) { + return OTS_FAILURE_MSG("Failed to read condition count"); + } + + for (uint16_t i = 0; i < condition_count; i++) { + uint32_t condition_offset = 0; + if (!subtable.ReadU32(&condition_offset)) { + return OTS_FAILURE_MSG("Failed to read condition offset"); + } + if (condition_offset < subtable.offset() || condition_offset >= length) { + return OTS_FAILURE_MSG("Offset out of range"); + } + if (!ParseConditionTable(font, data + condition_offset, length - condition_offset, + axis_count)) { + return OTS_FAILURE_MSG("Failed to parse condition table"); + } + } + + return true; +} + +bool ParseFeatureTableSubstitutionTable(const Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_lookups) { + Buffer subtable(data, length); + + uint16_t version_major = 0; + uint16_t version_minor = 0; + uint16_t substitution_count = 0; + const size_t kFeatureTableSubstitutionHeaderSize = 3 * sizeof(uint16_t); + + if (!subtable.ReadU16(&version_major) || + !subtable.ReadU16(&version_minor) || + !subtable.ReadU16(&substitution_count)) { + return OTS_FAILURE_MSG("Failed to read feature table substitution table header"); + } + + for (uint16_t i = 0; i < substitution_count; i++) { + uint16_t feature_index = 0; + uint32_t alternate_feature_table_offset = 0; + const size_t kFeatureTableSubstitutionRecordSize = sizeof(uint16_t) + sizeof(uint32_t); + + if (!subtable.ReadU16(&feature_index) || + !subtable.ReadU32(&alternate_feature_table_offset)) { + return OTS_FAILURE_MSG("Failed to read feature table substitution record"); + } + + if (alternate_feature_table_offset < kFeatureTableSubstitutionHeaderSize + + kFeatureTableSubstitutionRecordSize * substitution_count || + alternate_feature_table_offset >= length) { + return OTS_FAILURE_MSG("Invalid alternate feature table offset"); + } + + if (!ParseFeatureTable(font, data + alternate_feature_table_offset, + length - alternate_feature_table_offset, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse alternate feature table"); + } + } + + return true; +} + +bool ParseFeatureVariationsTable(const Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_lookups) { + Buffer subtable(data, length); + + uint16_t version_major = 0; + uint16_t version_minor = 0; + uint32_t feature_variation_record_count = 0; + + if (!subtable.ReadU16(&version_major) || + !subtable.ReadU16(&version_minor) || + !subtable.ReadU32(&feature_variation_record_count)) { + return OTS_FAILURE_MSG("Failed to read feature variations table header"); + } + + OpenTypeFVAR* fvar = static_cast(font->GetTypedTable(OTS_TAG_FVAR)); + if (!fvar) { + return OTS_FAILURE_MSG("Not a variation font"); + } + const uint16_t axis_count = fvar->AxisCount(); + + const size_t kEndOfFeatureVariationRecords = + 2 * sizeof(uint16_t) + sizeof(uint32_t) + + feature_variation_record_count * 2 * sizeof(uint32_t); + + for (uint32_t i = 0; i < feature_variation_record_count; i++) { + uint32_t condition_set_offset = 0; + uint32_t feature_table_substitution_offset = 0; + if (!subtable.ReadU32(&condition_set_offset) || + !subtable.ReadU32(&feature_table_substitution_offset)) { + return OTS_FAILURE_MSG("Failed to read feature variation record"); + } + + if (condition_set_offset) { + if (condition_set_offset < kEndOfFeatureVariationRecords || + condition_set_offset >= length) { + return OTS_FAILURE_MSG("Condition set offset out of range"); + } + if (!ParseConditionSetTable(font, data + condition_set_offset, + length - condition_set_offset, + axis_count)) { + return OTS_FAILURE_MSG("Failed to parse condition set table"); + } + } + + if (feature_table_substitution_offset) { + if (feature_table_substitution_offset < kEndOfFeatureVariationRecords || + feature_table_substitution_offset >= length) { + return OTS_FAILURE_MSG("Feature table substitution offset out of range"); + } + if (!ParseFeatureTableSubstitutionTable(font, data + feature_table_substitution_offset, + length - feature_table_substitution_offset, + num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse feature table substitution table"); + } + } + } + + return true; +} + } // namespace ots #undef TABLE_NAME diff --git a/gfx/ots/src/layout.h b/gfx/ots/src/layout.h index d195646d4..d10a3be5b 100644 --- a/gfx/ots/src/layout.h +++ b/gfx/ots/src/layout.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -12,6 +12,8 @@ namespace ots { +// The maximum number of class value. +const uint16_t kMaxClassDefValue = 0xFFFF; struct LookupSubtableParser { struct TypeParser { @@ -70,6 +72,23 @@ bool ParseExtensionSubtable(const Font *font, const uint8_t *data, const size_t length, const LookupSubtableParser* parser); +// For feature variations table (in GSUB/GPOS v1.1) +bool ParseConditionTable(const Font *font, + const uint8_t *data, const size_t length, + const uint16_t axis_count); + +bool ParseConditionSetTable(const Font *font, + const uint8_t *data, const size_t length, + const uint16_t axis_count); + +bool ParseFeatureTableSubstitutionTable(const Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_lookups); + +bool ParseFeatureVariationsTable(const Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_lookups); + } // namespace ots #endif // OTS_LAYOUT_H_ diff --git a/gfx/ots/src/loca.cc b/gfx/ots/src/loca.cc index aae04c25a..4f322027d 100644 --- a/gfx/ots/src/loca.cc +++ b/gfx/ots/src/loca.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,84 +10,79 @@ // 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) { +bool OpenTypeLOCA::Parse(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"); + OpenTypeMAXP *maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + OpenTypeHEAD *head = static_cast( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); + if (!maxp || !head) { + return Error("Required maxp or head tables are missing"); } - const unsigned num_glyphs = font->maxp->num_glyphs; + const unsigned num_glyphs = maxp->num_glyphs; unsigned last_offset = 0; - loca->offsets.resize(num_glyphs + 1); + this->offsets.resize(num_glyphs + 1); // maxp->num_glyphs is uint16_t, thus the addition never overflows. - if (font->head->index_to_loc_format == 0) { + if (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); + return Error("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); + return Error("Out of order offset %d < %d for glyph %d", offset, last_offset, i); } last_offset = offset; - loca->offsets[i] = offset * 2; + this->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); + return Error("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); + return Error("Out of order offset %d < %d for glyph %d", offset, last_offset, i); } last_offset = offset; - loca->offsets[i] = offset; + this->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; +bool OpenTypeLOCA::Serialize(OTSStream *out) { + OpenTypeHEAD *head = static_cast( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); if (!head) { - return OTS_FAILURE_MSG("Missing head table in font needed by loca"); + return Error("Required head table is missing"); } if (head->index_to_loc_format == 0) { - for (unsigned i = 0; i < loca->offsets.size(); ++i) { - const uint16_t offset = static_cast(loca->offsets[i] >> 1); - if ((offset != (loca->offsets[i] >> 1)) || + for (unsigned i = 0; i < this->offsets.size(); ++i) { + const uint16_t offset = static_cast(this->offsets[i] >> 1); + if ((offset != (this->offsets[i] >> 1)) || !out->WriteU16(offset)) { - return OTS_FAILURE_MSG("Failed to write glyph offset for glyph %d", i); + return Error("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); + for (unsigned i = 0; i < this->offsets.size(); ++i) { + if (!out->WriteU32(this->offsets[i])) { + return Error("Failed to write glyph offset for glyph %d", i); } } } @@ -95,15 +90,4 @@ bool ots_loca_serialise(OTSStream *out, Font *font) { 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 index 255ef06ec..da3241842 100644 --- a/gfx/ots/src/loca.h +++ b/gfx/ots/src/loca.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -11,7 +11,14 @@ namespace ots { -struct OpenTypeLOCA { +class OpenTypeLOCA : public Table { + public: + explicit OpenTypeLOCA(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + std::vector offsets; }; diff --git a/gfx/ots/src/ltsh.cc b/gfx/ots/src/ltsh.cc index 5b34cf4ad..4c40c5eb7 100644 --- a/gfx/ots/src/ltsh.cc +++ b/gfx/ots/src/ltsh.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,89 +9,63 @@ // 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) { +bool OpenTypeLTSH::Parse(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"); + OpenTypeMAXP *maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table is missing"); } - OpenTypeLTSH *ltsh = new OpenTypeLTSH; - font->ltsh = ltsh; - uint16_t num_glyphs = 0; - if (!table.ReadU16(<sh->version) || + if (!table.ReadU16(&this->version) || !table.ReadU16(&num_glyphs)) { - return OTS_FAILURE_MSG("Failed to read ltsh header"); + return Error("Failed to read table header"); } - if (ltsh->version != 0) { - DROP_THIS_TABLE("bad version: %u", ltsh->version); - return true; + if (this->version != 0) { + return Drop("Unsupported version: %u", this->version); } - if (num_glyphs != font->maxp->num_glyphs) { - DROP_THIS_TABLE("bad num_glyphs: %u", num_glyphs); - return true; + if (num_glyphs != maxp->num_glyphs) { + return Drop("Bad numGlyphs: %u", num_glyphs); } - ltsh->ypels.reserve(num_glyphs); + this->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); + return Error("Failed to read pixels for glyph %d", i); } - ltsh->ypels.push_back(pel); + this->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(ltsh->ypels.size()); - if (num_ypels != ltsh->ypels.size() || - !out->WriteU16(ltsh->version) || +bool OpenTypeLTSH::Serialize(OTSStream *out) { + const uint16_t num_ypels = static_cast(this->ypels.size()); + if (num_ypels != this->ypels.size() || + !out->WriteU16(this->version) || !out->WriteU16(num_ypels)) { - return OTS_FAILURE_MSG("Failed to write pels size"); + return Error("Failed to write table header"); } 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); + if (!out->Write(&(this->ypels[i]), 1)) { + return Error("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; +bool OpenTypeLTSH::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; } } // namespace ots - -#undef TABLE_NAME -#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/ltsh.h b/gfx/ots/src/ltsh.h index 23d97d784..cc9fbf62d 100644 --- a/gfx/ots/src/ltsh.h +++ b/gfx/ots/src/ltsh.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -11,7 +11,16 @@ namespace ots { -struct OpenTypeLTSH { +class OpenTypeLTSH : public Table { + public: + explicit OpenTypeLTSH(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); + + private: uint16_t version; std::vector ypels; }; diff --git a/gfx/ots/src/math.cc b/gfx/ots/src/math.cc index 36417dc29..c94e3bee3 100644 --- a/gfx/ots/src/math.cc +++ b/gfx/ots/src/math.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Copyright (c) 2014-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -12,12 +12,7 @@ #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" +// http://www.microsoft.com/typography/otspec/math.htm namespace { @@ -48,11 +43,15 @@ const unsigned kMathValueRecordSize = 2 * 2; // PartFlags const unsigned kGlyphPartRecordSize = 5 * 2; +} // namespace + +namespace ots { + // Shared Table: MathValueRecord -bool ParseMathValueRecord(const ots::Font *font, - ots::Buffer* subtable, const uint8_t *data, - const size_t length) { +bool OpenTypeMATH::ParseMathValueRecord(ots::Buffer* subtable, + const uint8_t *data, + const size_t length) { // Check the Value field. if (!subtable->Skip(2)) { return OTS_FAILURE(); @@ -67,7 +66,7 @@ bool ParseMathValueRecord(const ots::Font *font, if (offset >= length) { return OTS_FAILURE(); } - if (!ots::ParseDeviceTable(font, data + offset, length - offset)) { + if (!ots::ParseDeviceTable(GetFont(), data + offset, length - offset)) { return OTS_FAILURE(); } } @@ -75,8 +74,8 @@ bool ParseMathValueRecord(const ots::Font *font, return true; } -bool ParseMathConstantsTable(const ots::Font *font, - const uint8_t *data, size_t length) { +bool OpenTypeMATH::ParseMathConstantsTable(const uint8_t *data, + size_t length) { ots::Buffer subtable(data, length); // Part 1: int16 or uint16 constants. @@ -146,7 +145,7 @@ bool ParseMathConstantsTable(const ots::Font *font, // // RadicalKernAfterDegree for (unsigned i = 0; i < static_cast(51); ++i) { - if (!ParseMathValueRecord(font, &subtable, data, length)) { + if (!ParseMathValueRecord(&subtable, data, length)) { return OTS_FAILURE(); } } @@ -160,11 +159,10 @@ bool ParseMathConstantsTable(const ots::Font *font, return true; } -bool ParseMathValueRecordSequenceForGlyphs(const ots::Font *font, - ots::Buffer* subtable, - const uint8_t *data, - const size_t length, - const uint16_t num_glyphs) { +bool OpenTypeMATH::ParseMathValueRecordSequenceForGlyphs(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; @@ -183,7 +181,7 @@ bool ParseMathValueRecordSequenceForGlyphs(const ots::Font *font, if (offset_coverage < sequence_end || offset_coverage >= length) { return OTS_FAILURE(); } - if (!ots::ParseCoverageTable(font, data + offset_coverage, + if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, length - offset_coverage, num_glyphs, sequence_count)) { return OTS_FAILURE(); @@ -191,7 +189,7 @@ bool ParseMathValueRecordSequenceForGlyphs(const ots::Font *font, // Check sequence. for (unsigned i = 0; i < sequence_count; ++i) { - if (!ParseMathValueRecord(font, subtable, data, length)) { + if (!ParseMathValueRecord(subtable, data, length)) { return OTS_FAILURE(); } } @@ -199,26 +197,23 @@ bool ParseMathValueRecordSequenceForGlyphs(const ots::Font *font, return true; } -bool ParseMathItalicsCorrectionInfoTable(const ots::Font *font, - const uint8_t *data, - size_t length, - const uint16_t num_glyphs) { +bool OpenTypeMATH::ParseMathItalicsCorrectionInfoTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { ots::Buffer subtable(data, length); - return ParseMathValueRecordSequenceForGlyphs(font, &subtable, data, length, + return ParseMathValueRecordSequenceForGlyphs(&subtable, data, length, num_glyphs); } -bool ParseMathTopAccentAttachmentTable(const ots::Font *font, - const uint8_t *data, - size_t length, - const uint16_t num_glyphs) { +bool OpenTypeMATH::ParseMathTopAccentAttachmentTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { ots::Buffer subtable(data, length); - return ParseMathValueRecordSequenceForGlyphs(font, &subtable, data, length, + return ParseMathValueRecordSequenceForGlyphs(&subtable, data, length, num_glyphs); } -bool ParseMathKernTable(const ots::Font *font, - const uint8_t *data, size_t length) { +bool OpenTypeMATH::ParseMathKernTable(const uint8_t *data, size_t length) { ots::Buffer subtable(data, length); // Check the Height count. @@ -229,14 +224,14 @@ bool ParseMathKernTable(const ots::Font *font, // Check the Correction Heights. for (unsigned i = 0; i < height_count; ++i) { - if (!ParseMathValueRecord(font, &subtable, data, length)) { + if (!ParseMathValueRecord(&subtable, data, length)) { return OTS_FAILURE(); } } // Check the Kern Values. for (unsigned i = 0; i <= height_count; ++i) { - if (!ParseMathValueRecord(font, &subtable, data, length)) { + if (!ParseMathValueRecord(&subtable, data, length)) { return OTS_FAILURE(); } } @@ -244,9 +239,9 @@ bool ParseMathKernTable(const ots::Font *font, return true; } -bool ParseMathKernInfoTable(const ots::Font *font, - const uint8_t *data, size_t length, - const uint16_t num_glyphs) { +bool OpenTypeMATH::ParseMathKernInfoTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { ots::Buffer subtable(data, length); // Check the header. @@ -267,7 +262,7 @@ bool ParseMathKernInfoTable(const ots::Font *font, if (offset_coverage < sequence_end || offset_coverage >= length) { return OTS_FAILURE(); } - if (!ots::ParseCoverageTable(font, data + offset_coverage, length - offset_coverage, + if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, length - offset_coverage, num_glyphs, sequence_count)) { return OTS_FAILURE(); } @@ -282,7 +277,7 @@ bool ParseMathKernInfoTable(const ots::Font *font, } if (offset_math_kern) { if (offset_math_kern < sequence_end || offset_math_kern >= length || - !ParseMathKernTable(font, data + offset_math_kern, + !ParseMathKernTable(data + offset_math_kern, length - offset_math_kern)) { return OTS_FAILURE(); } @@ -293,9 +288,9 @@ bool ParseMathKernInfoTable(const ots::Font *font, return true; } -bool ParseMathGlyphInfoTable(const ots::Font *font, - const uint8_t *data, size_t length, - const uint16_t num_glyphs) { +bool OpenTypeMATH::ParseMathGlyphInfoTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { ots::Buffer subtable(data, length); // Check Header. @@ -318,7 +313,7 @@ bool ParseMathGlyphInfoTable(const ots::Font *font, if (offset_math_italics_correction_info >= length || offset_math_italics_correction_info < kMathGlyphInfoHeaderSize || !ParseMathItalicsCorrectionInfoTable( - font, data + offset_math_italics_correction_info, + data + offset_math_italics_correction_info, length - offset_math_italics_correction_info, num_glyphs)) { return OTS_FAILURE(); @@ -327,7 +322,7 @@ bool ParseMathGlyphInfoTable(const ots::Font *font, if (offset_math_top_accent_attachment) { if (offset_math_top_accent_attachment >= length || offset_math_top_accent_attachment < kMathGlyphInfoHeaderSize || - !ParseMathTopAccentAttachmentTable(font, data + + !ParseMathTopAccentAttachmentTable(data + offset_math_top_accent_attachment, length - offset_math_top_accent_attachment, @@ -338,7 +333,7 @@ bool ParseMathGlyphInfoTable(const ots::Font *font, if (offset_extended_shaped_coverage) { if (offset_extended_shaped_coverage >= length || offset_extended_shaped_coverage < kMathGlyphInfoHeaderSize || - !ots::ParseCoverageTable(font, data + offset_extended_shaped_coverage, + !ots::ParseCoverageTable(GetFont(), data + offset_extended_shaped_coverage, length - offset_extended_shaped_coverage, num_glyphs)) { return OTS_FAILURE(); @@ -347,7 +342,7 @@ bool ParseMathGlyphInfoTable(const ots::Font *font, if (offset_math_kern_info) { if (offset_math_kern_info >= length || offset_math_kern_info < kMathGlyphInfoHeaderSize || - !ParseMathKernInfoTable(font, data + offset_math_kern_info, + !ParseMathKernInfoTable(data + offset_math_kern_info, length - offset_math_kern_info, num_glyphs)) { return OTS_FAILURE(); } @@ -356,14 +351,14 @@ bool ParseMathGlyphInfoTable(const ots::Font *font, return true; } -bool ParseGlyphAssemblyTable(const ots::Font *font, - const uint8_t *data, - size_t length, const uint16_t num_glyphs) { +bool OpenTypeMATH::ParseGlyphAssemblyTable(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) || + if (!ParseMathValueRecord(&subtable, data, length) || !subtable.ReadU16(&part_count)) { return OTS_FAILURE(); } @@ -384,19 +379,19 @@ bool ParseGlyphAssemblyTable(const ots::Font *font, return OTS_FAILURE(); } if (glyph >= num_glyphs) { - return OTS_FAILURE_MSG("bad glyph ID: %u", glyph); + return Error("bad glyph ID: %u", glyph); } if (part_flags & ~0x00000001) { - return OTS_FAILURE_MSG("unknown part flag: %u", part_flags); + return Error("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) { +bool OpenTypeMATH::ParseMathGlyphConstructionTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { ots::Buffer subtable(data, length); // Check the header. @@ -419,7 +414,7 @@ bool ParseMathGlyphConstructionTable(const ots::Font *font, offset_glyph_assembly < sequence_end) { return OTS_FAILURE(); } - if (!ParseGlyphAssemblyTable(font, data + offset_glyph_assembly, + if (!ParseGlyphAssemblyTable(data + offset_glyph_assembly, length - offset_glyph_assembly, num_glyphs)) { return OTS_FAILURE(); } @@ -433,21 +428,20 @@ bool ParseMathGlyphConstructionTable(const ots::Font *font, return OTS_FAILURE(); } if (glyph >= num_glyphs) { - return OTS_FAILURE_MSG("bad glyph ID: %u", glyph); + return Error("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) { +bool OpenTypeMATH::ParseMathGlyphConstructionSequence(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; @@ -457,7 +451,7 @@ bool ParseMathGlyphConstructionSequence(const ots::Font *font, if (offset_coverage < sequence_end || offset_coverage >= length) { return OTS_FAILURE(); } - if (!ots::ParseCoverageTable(font, data + offset_coverage, + if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, length - offset_coverage, num_glyphs, glyph_count)) { return OTS_FAILURE(); @@ -471,7 +465,7 @@ bool ParseMathGlyphConstructionSequence(const ots::Font *font, } if (offset_glyph_construction < sequence_end || offset_glyph_construction >= length || - !ParseMathGlyphConstructionTable(font, data + offset_glyph_construction, + !ParseMathGlyphConstructionTable(data + offset_glyph_construction, length - offset_glyph_construction, num_glyphs)) { return OTS_FAILURE(); @@ -481,9 +475,9 @@ bool ParseMathGlyphConstructionSequence(const ots::Font *font, return true; } -bool ParseMathVariantsTable(const ots::Font *font, - const uint8_t *data, - size_t length, const uint16_t num_glyphs) { +bool OpenTypeMATH::ParseMathVariantsTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { ots::Buffer subtable(data, length); // Check the header. @@ -505,11 +499,11 @@ bool ParseMathVariantsTable(const ots::Font *font, return OTS_FAILURE(); } - if (!ParseMathGlyphConstructionSequence(font, &subtable, data, length, num_glyphs, + if (!ParseMathGlyphConstructionSequence(&subtable, data, length, num_glyphs, offset_vert_glyph_coverage, vert_glyph_count, sequence_end) || - !ParseMathGlyphConstructionSequence(font, &subtable, data, length, num_glyphs, + !ParseMathGlyphConstructionSequence(&subtable, data, length, num_glyphs, offset_horiz_glyph_coverage, horiz_glyph_count, sequence_end)) { @@ -519,37 +513,24 @@ bool ParseMathVariantsTable(const ots::Font *font, 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) { +bool OpenTypeMATH::Parse(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(); + OpenTypeMAXP *maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); } - const uint16_t num_glyphs = font->maxp->num_glyphs; + const uint16_t num_glyphs = 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; + return Drop("bad MATH version"); } uint16_t offset_math_constants = 0; @@ -567,53 +548,37 @@ bool ots_math_parse(Font *font, const uint8_t *data, size_t length) { offset_math_glyph_info < kMathHeaderSize || offset_math_variants >= length || offset_math_variants < kMathHeaderSize) { - DROP_THIS_TABLE("bad offset in MATH header"); - return true; + return Drop("bad offset in MATH header"); } - if (!ParseMathConstantsTable(font, data + offset_math_constants, + if (!ParseMathConstantsTable(data + offset_math_constants, length - offset_math_constants)) { - DROP_THIS_TABLE("failed to parse MathConstants table"); - return true; + return Drop("failed to parse MathConstants table"); } - if (!ParseMathGlyphInfoTable(font, data + offset_math_glyph_info, + if (!ParseMathGlyphInfoTable(data + offset_math_glyph_info, length - offset_math_glyph_info, num_glyphs)) { - DROP_THIS_TABLE("failed to parse MathGlyphInfo table"); - return true; + return Drop("failed to parse MathGlyphInfo table"); } - if (!ParseMathVariantsTable(font, data + offset_math_variants, + if (!ParseMathVariantsTable(data + offset_math_variants, length - offset_math_variants, num_glyphs)) { - DROP_THIS_TABLE("failed to parse MathVariants table"); - return true; + return Drop("failed to parse MathVariants table"); } - math->data = data; - math->length = length; + this->m_data = data; + this->m_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)) { +bool OpenTypeMATH::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_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; +bool OpenTypeMATH::ShouldSerialize() { + return Table::ShouldSerialize() && this->m_data != NULL; } } // namespace ots - -#undef TABLE_NAME -#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/math_.h b/gfx/ots/src/math_.h index 91c54dbe1..875cacd4d 100644 --- a/gfx/ots/src/math_.h +++ b/gfx/ots/src/math_.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Copyright (c) 2014-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,14 +9,58 @@ namespace ots { -struct OpenTypeMATH { - OpenTypeMATH() - : data(NULL), - length(0) { - } +class OpenTypeMATH : public Table { + public: + explicit OpenTypeMATH(Font *font, uint32_t tag) + : Table(font, tag, tag), + m_data(NULL), + m_length(0) { } - const uint8_t *data; - size_t length; + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); + + private: + bool ParseMathValueRecord(ots::Buffer* subtable, + const uint8_t *data, + const size_t length); + bool ParseMathConstantsTable(const uint8_t *data, size_t length); + bool ParseMathValueRecordSequenceForGlyphs(ots::Buffer* subtable, + const uint8_t *data, + const size_t length, + const uint16_t num_glyphs); + bool ParseMathItalicsCorrectionInfoTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs); + bool ParseMathTopAccentAttachmentTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs); + bool ParseMathKernTable(const uint8_t *data, size_t length); + bool ParseMathKernInfoTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs); + bool ParseMathGlyphInfoTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs); + bool ParseGlyphAssemblyTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs); + bool ParseMathGlyphConstructionTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs); + bool ParseMathGlyphConstructionSequence(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); + bool ParseMathVariantsTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs); + + const uint8_t *m_data; + size_t m_length; }; } // namespace ots diff --git a/gfx/ots/src/maxp.cc b/gfx/ots/src/maxp.cc index 41e29a745..232e4a988 100644 --- a/gfx/ots/src/maxp.cc +++ b/gfx/ots/src/maxp.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,119 +7,97 @@ // 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) { +bool OpenTypeMAXP::Parse(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"); + return Error("Failed to read table version"); } if (version >> 16 > 1) { - return OTS_FAILURE_MSG("Bad maxp version %d", version); + return Error("Unsupported table version 0x%x", version); } - if (!table.ReadU16(&maxp->num_glyphs)) { - return OTS_FAILURE_MSG("Failed to read number of glyphs from maxp table"); + if (!table.ReadU16(&this->num_glyphs)) { + return Error("Failed to read numGlyphs"); } - if (!maxp->num_glyphs) { - return OTS_FAILURE_MSG("Bad number of glyphs 0 in maxp table"); + if (!this->num_glyphs) { + return Error("numGlyphs is 0"); } 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"); + this->version_1 = true; + if (!table.ReadU16(&this->max_points) || + !table.ReadU16(&this->max_contours) || + !table.ReadU16(&this->max_c_points) || + !table.ReadU16(&this->max_c_contours) || + !table.ReadU16(&this->max_zones) || + !table.ReadU16(&this->max_t_points) || + !table.ReadU16(&this->max_storage) || + !table.ReadU16(&this->max_fdefs) || + !table.ReadU16(&this->max_idefs) || + !table.ReadU16(&this->max_stack) || + !table.ReadU16(&this->max_size_glyf_instructions) || + !table.ReadU16(&this->max_c_components) || + !table.ReadU16(&this->max_c_depth)) { + return Error("Failed to read version 1 table data"); } - if (maxp->max_zones == 0) { + if (this->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) { + Warning("Bad maxZones: %u", this->max_zones); + this->max_zones = 1; + } else if (this->max_zones == 3) { // workaround for Ecolier-*.ttf fonts. - OTS_WARNING("bad max_zones: %u", maxp->max_zones); - maxp->max_zones = 2; + Warning("Bad maxZones: %u", this->max_zones); + this->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); + if ((this->max_zones != 1) && (this->max_zones != 2)) { + return Error("Bad maxZones: %u", this->max_zones); } } else { - maxp->version_1 = false; + this->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"); +bool OpenTypeMAXP::Serialize(OTSStream *out) { + if (!out->WriteU32(this->version_1 ? 0x00010000 : 0x00005000) || + !out->WriteU16(this->num_glyphs)) { + return Error("Failed to write version or numGlyphs"); } - if (!maxp->version_1) return true; + if (!this->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(this->max_points) || + !out->WriteU16(this->max_contours) || + !out->WriteU16(this->max_c_points) || + !out->WriteU16(this->max_c_contours)) { + return Error("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(this->max_zones) || + !out->WriteU16(this->max_t_points) || + !out->WriteU16(this->max_storage) || + !out->WriteU16(this->max_fdefs) || + !out->WriteU16(this->max_idefs) || + !out->WriteU16(this->max_stack) || + !out->WriteU16(this->max_size_glyf_instructions)) { + return Error("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"); + if (!out->WriteU16(this->max_c_components) || + !out->WriteU16(this->max_c_depth)) { + return Error("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 index efca0c91a..99dbdc439 100644 --- a/gfx/ots/src/maxp.h +++ b/gfx/ots/src/maxp.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,7 +9,14 @@ namespace ots { -struct OpenTypeMAXP { +class OpenTypeMAXP : public Table { + public: + explicit OpenTypeMAXP(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + uint16_t num_glyphs; bool version_1; diff --git a/gfx/ots/src/metrics.cc b/gfx/ots/src/metrics.cc index cd89f4555..b49d73c5d 100644 --- a/gfx/ots/src/metrics.cc +++ b/gfx/ots/src/metrics.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -11,149 +11,163 @@ // 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"); +bool OpenTypeMetricsHeader::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + // Skip already read version. + if (!table.Skip(4)) { + return false; + } + + if (!table.ReadS16(&this->ascent) || + !table.ReadS16(&this->descent) || + !table.ReadS16(&this->linegap) || + !table.ReadU16(&this->adv_width_max) || + !table.ReadS16(&this->min_sb1) || + !table.ReadS16(&this->min_sb2) || + !table.ReadS16(&this->max_extent) || + !table.ReadS16(&this->caret_slope_rise) || + !table.ReadS16(&this->caret_slope_run) || + !table.ReadS16(&this->caret_offset)) { + return Error("Failed to read table"); } - if (header->ascent < 0) { - OTS_WARNING("bad ascent: %d", header->ascent); - header->ascent = 0; + if (this->ascent < 0) { + Warning("bad ascent: %d", this->ascent); + this->ascent = 0; } - if (header->linegap < 0) { - OTS_WARNING("bad linegap: %d", header->linegap); - header->linegap = 0; + if (this->linegap < 0) { + Warning("bad linegap: %d", this->linegap); + this->linegap = 0; } - if (!font->head) { - return OTS_FAILURE_MSG("Missing head font table"); + OpenTypeHEAD *head = static_cast( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); + if (!head) { + return Error("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; + if (!(head->mac_style & 2) && + (this->caret_offset != 0)) { + Warning("bad caret offset: %d", this->caret_offset); + this->caret_offset = 0; } // skip the reserved bytes - if (!table->Skip(8)) { - return OTS_FAILURE_MSG("Failed to skip reserverd bytes"); + if (!table.Skip(8)) { + return Error("Failed to read reserverd bytes"); } int16_t data_format; - if (!table->ReadS16(&data_format)) { - return OTS_FAILURE_MSG("Failed to read data format"); + if (!table.ReadS16(&data_format)) { + return Error("Failed to read metricDataFormat"); } if (data_format) { - return OTS_FAILURE_MSG("Bad data format %d", data_format); + return Error("Bad metricDataFormat: %d", data_format); } - if (!table->ReadU16(&header->num_metrics)) { - return OTS_FAILURE_MSG("Failed to read number of metrics"); + if (!table.ReadU16(&this->num_metrics)) { + return Error("Failed to read number of metrics"); } - if (!font->maxp) { - return OTS_FAILURE_MSG("Missing maxp font table"); + OpenTypeMAXP *maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Missing maxp font table"); } - if (header->num_metrics > font->maxp->num_glyphs) { - return OTS_FAILURE_MSG("Bad number of metrics %d", header->num_metrics); + if (this->num_metrics > maxp->num_glyphs) { + return Error("Bad number of metrics %d", this->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) || +bool OpenTypeMetricsHeader::Serialize(OTSStream *out) { + if (!out->WriteU32(this->version) || + !out->WriteS16(this->ascent) || + !out->WriteS16(this->descent) || + !out->WriteS16(this->linegap) || + !out->WriteU16(this->adv_width_max) || + !out->WriteS16(this->min_sb1) || + !out->WriteS16(this->min_sb2) || + !out->WriteS16(this->max_extent) || + !out->WriteS16(this->caret_slope_rise) || + !out->WriteS16(this->caret_slope_run) || + !out->WriteS16(this->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"); + !out->WriteU16(this->num_metrics)) { + return Error("Failed to write metrics"); } return true; } -bool ParseMetricsTable(const ots::Font *font, - Buffer *table, - const uint16_t num_glyphs, - const OpenTypeMetricsHeader *header, - OpenTypeMetricsTable *metrics) { +bool OpenTypeMetricsTable::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + // OpenTypeMetricsHeader is a superclass of both 'hhea' and 'vhea', + // so the cast here is OK, whichever m_header_tag we have. + OpenTypeMetricsHeader *header = static_cast( + GetFont()->GetTypedTable(m_header_tag)); + if (!header) { + return Error("Required %c%c%c%c table missing", OTS_UNTAG(m_header_tag)); + } // |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); + OpenTypeMAXP *maxp = static_cast( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + if (num_metrics > maxp->num_glyphs) { + return Error("Bad number of metrics %d", num_metrics); } if (!num_metrics) { - return OTS_FAILURE_MSG("No metrics!"); + return Error("No metrics!"); } - const unsigned num_sbs = num_glyphs - num_metrics; + const unsigned num_sbs = maxp->num_glyphs - num_metrics; - metrics->entries.reserve(num_metrics); + this->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); + if (!table.ReadU16(&adv) || !table.ReadS16(&sb)) { + return Error("Failed to read metric %d", i); } - metrics->entries.push_back(std::make_pair(adv, sb)); + this->entries.push_back(std::make_pair(adv, sb)); } - metrics->sbs.reserve(num_sbs); + this->sbs.reserve(num_sbs); for (unsigned i = 0; i < num_sbs; ++i) { int16_t sb; - if (!table->ReadS16(&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); + return Error("Failed to read side bearing %d", i + num_metrics); } - metrics->sbs.push_back(sb); + this->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); +bool OpenTypeMetricsTable::Serialize(OTSStream *out) { + for (unsigned i = 0; i < this->entries.size(); ++i) { + if (!out->WriteU16(this->entries[i].first) || + !out->WriteS16(this->entries[i].second)) { + return Error("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()); + for (unsigned i = 0; i < this->sbs.size(); ++i) { + if (!out->WriteS16(this->sbs[i])) { + return Error("Failed to write side bearing %ld", i + this->entries.size()); } } @@ -161,5 +175,3 @@ bool SerialiseMetricsTable(const ots::Font *font, } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/metrics.h b/gfx/ots/src/metrics.h index 767be2fdb..efe14c070 100644 --- a/gfx/ots/src/metrics.h +++ b/gfx/ots/src/metrics.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,7 +13,14 @@ namespace ots { -struct OpenTypeMetricsHeader { +class OpenTypeMetricsHeader : public Table { + public: + explicit OpenTypeMetricsHeader(Font *font, uint32_t tag, uint32_t type) + : Table(font, tag, type) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + uint32_t version; int16_t ascent; int16_t descent; @@ -28,27 +35,22 @@ struct OpenTypeMetricsHeader { uint16_t num_metrics; }; -struct OpenTypeMetricsTable { +struct OpenTypeMetricsTable : public Table { + public: + explicit OpenTypeMetricsTable(Font *font, uint32_t tag, uint32_t type, + uint32_t header_tag) + : Table(font, tag, type), m_header_tag(header_tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + private: + uint32_t m_header_tag; + std::vector > entries; std::vector 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 index f8e60d5f8..962a86a5b 100644 --- a/gfx/ots/src/moz.build +++ b/gfx/ots/src/moz.build @@ -9,41 +9,52 @@ EXPORTS += [ '../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 += [ + 'avar.cc', 'cff.cc', - 'cff_type2_charstring.cc', + 'cff_charstring.cc', 'cmap.cc', + 'cvar.cc', 'cvt.cc', + 'feat.cc', 'fpgm.cc', + 'fvar.cc', + 'gasp.cc', + 'gdef.cc', + 'glat.cc', + 'gloc.cc', 'glyf.cc', + 'gpos.cc', + 'gsub.cc', + 'gvar.cc', + 'hdmx.cc', 'head.cc', 'hhea.cc', 'hmtx.cc', + 'hvar.cc', + 'kern.cc', 'layout.cc', 'loca.cc', + 'ltsh.cc', + 'math.cc', 'maxp.cc', 'metrics.cc', + 'mvar.cc', 'name.cc', 'os2.cc', 'ots.cc', 'post.cc', 'prep.cc', + 'sile.cc', + 'silf.cc', + 'sill.cc', + 'stat.cc', + 'variations.cc', + 'vdmx.cc', 'vhea.cc', 'vmtx.cc', + 'vorg.cc', + 'vvar.cc', ] # We allow warnings for third-party code that can be updated from upstream. @@ -53,8 +64,14 @@ FINAL_LIBRARY = 'gkmedias' DEFINES['PACKAGE_VERSION'] = '"moz"' DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"' +DEFINES['OTS_GRAPHITE'] = 1 +DEFINES['OTS_VARIATIONS'] = 1 USE_LIBS += [ 'brotli', 'woff2', ] + +LOCAL_INCLUDES += [ + '/modules/woff2/src', +] diff --git a/gfx/ots/src/mvar.cc b/gfx/ots/src/mvar.cc new file mode 100644 index 000000000..d021203d3 --- /dev/null +++ b/gfx/ots/src/mvar.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2018 The OTS 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 "mvar.h" + +#include "variations.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeMVAR +// ----------------------------------------------------------------------------- + +bool OpenTypeMVAR::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + + uint16_t majorVersion; + uint16_t minorVersion; + uint16_t reserved; + uint16_t valueRecordSize; + uint16_t valueRecordCount; + uint16_t itemVariationStoreOffset; + + if (!table.ReadU16(&majorVersion) || + !table.ReadU16(&minorVersion) || + !table.ReadU16(&reserved) || + !table.ReadU16(&valueRecordSize) || + !table.ReadU16(&valueRecordCount) || + !table.ReadU16(&itemVariationStoreOffset)) { + return DropVariations("Failed to read table header"); + } + + if (majorVersion != 1) { + return DropVariations("Unknown table version"); + } + + if (reserved != 0) { + Warning("Expected reserved=0"); + } + + // The spec says that valueRecordSize "must be greater than zero", + // but we don't enforce this in the case where valueRecordCount + // is zero. + // The minimum size for a valueRecord to be valid is 8, for the + // three fields currently defined in the record (see below). + if (valueRecordSize < 8) { + if (valueRecordCount != 0) { + return DropVariations("Value record size too small"); + } + } + + if (valueRecordCount == 0) { + if (itemVariationStoreOffset != 0) { + // The spec says "if valueRecordCount is zero, set to zero", + // but having a variation store even when record count is zero + // should be harmless -- it just won't be useful for anything. + // But we don't need to reject altogether. + Warning("Unexpected item variation store"); + } + } else { + if (itemVariationStoreOffset < table.offset() || itemVariationStoreOffset > length) { + return DropVariations("Invalid item variation store offset"); + } + if (!ParseItemVariationStore(GetFont(), data + itemVariationStoreOffset, + length - itemVariationStoreOffset)) { + return DropVariations("Failed to parse item variation store"); + } + } + + uint32_t prevTag = 0; + size_t offset = table.offset(); + for (unsigned i = 0; i < valueRecordCount; i++) { + uint32_t tag; + uint16_t deltaSetOuterIndex, deltaSetInnerIndex; + if (!table.ReadU32(&tag) || + !table.ReadU16(&deltaSetOuterIndex) || + !table.ReadU16(&deltaSetInnerIndex)) { + return DropVariations("Failed to read value record"); + } + if (tag <= prevTag) { + return DropVariations("Invalid or out-of-order value tag"); + } + prevTag = tag; + // Adjust offset in case additional fields have been added to the + // valueRecord by a new minor version (allowed by spec). + offset += valueRecordSize; + table.set_offset(offset); + } + + this->m_data = data; + this->m_length = length; + + return true; +} + +bool OpenTypeMVAR::Serialize(OTSStream* out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write MVAR table"); + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/mvar.h b/gfx/ots/src/mvar.h new file mode 100644 index 000000000..81fb6155d --- /dev/null +++ b/gfx/ots/src/mvar.h @@ -0,0 +1,31 @@ +// Copyright (c) 2018 The OTS 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_MVAR_H_ +#define OTS_MVAR_H_ + +#include "ots.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeMVAR Interface +// ----------------------------------------------------------------------------- + +class OpenTypeMVAR : public Table { + public: + explicit OpenTypeMVAR(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + const uint8_t *m_data; + size_t m_length; +}; + +} // namespace ots + +#endif // OTS_MVAR_H_ diff --git a/gfx/ots/src/name.cc b/gfx/ots/src/name.cc index e55be7537..11deeecaa 100644 --- a/gfx/ots/src/name.cc +++ b/gfx/ots/src/name.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,8 +10,6 @@ // name - Naming Table // http://www.microsoft.com/typography/otspec/name.htm -#define TABLE_NAME "name" - namespace { bool ValidInPsName(char c) { @@ -56,25 +54,22 @@ void AssignToUtf16BeFromAscii(std::string* target, namespace ots { -bool ots_name_parse(Font *font, const uint8_t* data, size_t length) { +bool OpenTypeNAME::Parse(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); + return Error("Failed to read table format or bad format %d", format); } uint16_t count = 0; if (!table.ReadU16(&count)) { - return OTS_FAILURE_MSG("Failed to read name count"); + return Error("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"); + return Error("Failed to read or bad stringOffset"); } const char* string_base = reinterpret_cast(data) + string_offset; @@ -94,7 +89,7 @@ bool ots_name_parse(Font *font, const uint8_t* data, size_t length) { !table.ReadU16(&rec.name_id) || !table.ReadU16(&name_length) || !table.ReadU16(&name_offset)) { - return OTS_FAILURE_MSG("Failed to read name entry %d", i); + return Error("Failed to read name entry %d", i); } // check platform & encoding, discard names with unknown values switch (rec.platform_id) { @@ -148,40 +143,49 @@ bool ots_name_parse(Font *font, const uint8_t* data, size_t length) { } } - if (!name->names.empty() && !(name->names.back() < rec)) { - OTS_WARNING("name records are not sorted."); + if (!this->names.empty() && !(this->names.back() < rec)) { + Warning("name records are not sorted."); sort_required = true; } - name->names.push_back(rec); + this->names.push_back(rec); + this->name_ids.insert(rec.name_id); } 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"); + return Error("Failed to read langTagCount"); } 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"); + return Error("Faile to read length or offset for langTagRecord %d", i); } const unsigned tag_end = static_cast(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); + return Error("bad end of tag %d > %ld for langTagRecord %d", tag_end, length, i); + } + // Lang tag is BCP 47 tag per the spec, the recommonded BCP 47 max tag + // length is 35: + // https://tools.ietf.org/html/bcp47#section-4.4.1 + // We are being too generous and allowing for 100 (multiplied by 2 since + // this is UTF-16 string). + if (tag_length > 100 * 2) { + return Error("Too long language tag for LangTagRecord %d: %d", i, tag_length); } std::string tag(string_base + tag_offset, tag_length); - name->lang_tags.push_back(tag); + this->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); + return Error("Bad table offset %ld > %d", table.offset(), string_offset); } // check existence of required name strings (synthesize if necessary) @@ -207,17 +211,16 @@ bool ots_name_parse(Font *font, const uint8_t* data, size_t length) { // if not, we'll add our fixed versions here bool mac_name[kStdNameCount] = { 0 }; bool win_name[kStdNameCount] = { 0 }; - for (std::vector::iterator name_iter = name->names.begin(); - name_iter != name->names.end(); ++name_iter) { - const uint16_t id = name_iter->name_id; + for (const auto& name : this->names) { + const uint16_t id = name.name_id; if (id >= kStdNameCount || kStdNames[id] == NULL) { continue; } - if (name_iter->platform_id == 1) { + if (name.platform_id == 1) { mac_name[id] = true; continue; } - if (name_iter->platform_id == 3) { + if (name.platform_id == 3) { win_name[id] = true; continue; } @@ -236,49 +239,41 @@ bool ots_name_parse(Font *font, const uint8_t* data, size_t length) { 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); + this->names.push_back(mac_rec); + this->names.push_back(win_rec); sort_required = true; } } if (sort_required) { - std::sort(name->names.begin(), name->names.end()); + std::sort(this->names.begin(), this->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(name->names.size()); - uint16_t lang_tag_count = static_cast(name->lang_tags.size()); +bool OpenTypeNAME::Serialize(OTSStream* out) { + uint16_t name_count = static_cast(this->names.size()); + uint16_t lang_tag_count = static_cast(this->lang_tags.size()); uint16_t format = 0; size_t string_offset = 6 + name_count * 12; - if (name->lang_tags.size() > 0) { + if (this->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); + return Error("Bad stringOffset: %ld", string_offset); } if (!out->WriteU16(format) || !out->WriteU16(name_count) || !out->WriteU16(static_cast(string_offset))) { - return OTS_FAILURE_MSG("Failed to write name header"); + return Error("Failed to write name header"); } std::string string_data; - for (std::vector::const_iterator name_iter = name->names.begin(); - name_iter != name->names.end(); ++name_iter) { - const NameRecord& rec = *name_iter; + for (const auto& rec : this->names) { if (string_data.size() + rec.text.size() > std::numeric_limits::max() || !out->WriteU16(rec.platform_id) || @@ -287,44 +282,77 @@ bool ots_name_serialise(OTSStream* out, Font *font) { !out->WriteU16(rec.name_id) || !out->WriteU16(static_cast(rec.text.size())) || !out->WriteU16(static_cast(string_data.size())) ) { - return OTS_FAILURE_MSG("Faile to write name entry"); + return Error("Faile to write nameRecord"); } string_data.append(rec.text); } if (format == 1) { if (!out->WriteU16(lang_tag_count)) { - return OTS_FAILURE_MSG("Faile to write language tag count"); + return Error("Faile to write langTagCount"); } - for (std::vector::const_iterator tag_iter = - name->lang_tags.begin(); - tag_iter != name->lang_tags.end(); ++tag_iter) { - if (string_data.size() + tag_iter->size() > + for (const auto& tag : this->lang_tags) { + if (string_data.size() + tag.size() > std::numeric_limits::max() || - !out->WriteU16(static_cast(tag_iter->size())) || + !out->WriteU16(static_cast(tag.size())) || !out->WriteU16(static_cast(string_data.size()))) { - return OTS_FAILURE_MSG("Failed to write string"); + return Error("Failed to write langTagRecord"); } - string_data.append(*tag_iter); + string_data.append(tag); } } if (!out->Write(string_data.data(), string_data.size())) { - return OTS_FAILURE_MSG("Faile to write string data"); + return Error("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; +bool OpenTypeNAME::IsValidNameId(uint16_t nameID, bool addIfMissing) { + if (addIfMissing && !this->name_ids.count(nameID)) { + bool added_unicode = false; + bool added_macintosh = false; + bool added_windows = false; + const size_t names_size = this->names.size(); // original size + for (size_t i = 0; i < names_size; ++i) switch (names[i].platform_id) { + case 0: + if (!added_unicode) { + // If there is an existing NameRecord with platform_id == 0 (Unicode), + // then add a NameRecord for the the specified nameID with arguments + // 0 (Unicode), 0 (v1.0), 0 (unspecified language). + this->names.emplace_back(0, 0, 0, nameID); + this->names.back().text = "NoName"; + added_unicode = true; + } + break; + case 1: + if (!added_macintosh) { + // If there is an existing NameRecord with platform_id == 1 (Macintosh), + // then add a NameRecord for the specified nameID with arguments + // 1 (Macintosh), 0 (Roman), 0 (English). + this->names.emplace_back(1, 0, 0, nameID); + this->names.back().text = "NoName"; + added_macintosh = true; + } + break; + case 3: + if (!added_windows) { + // If there is an existing NameRecord with platform_id == 3 (Windows), + // then add a NameRecord for the specified nameID with arguments + // 3 (Windows), 1 (UCS), 1033 (US English). + this->names.emplace_back(3, 1, 1033, nameID); + this->names.back().text = "NoName"; + added_windows = true; + } + break; + } + if (added_unicode || added_macintosh || added_windows) { + std::sort(this->names.begin(), this->names.end()); + this->name_ids.insert(nameID); + } + } + return this->name_ids.count(nameID); } } // namespace - -#undef TABLE_NAME diff --git a/gfx/ots/src/name.h b/gfx/ots/src/name.h index a11965f46..68c7ac096 100644 --- a/gfx/ots/src/name.h +++ b/gfx/ots/src/name.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,6 +9,7 @@ #include #include #include +#include #include "ots.h" @@ -43,9 +44,19 @@ struct NameRecord { } }; -struct OpenTypeNAME { +class OpenTypeNAME : public Table { + public: + explicit OpenTypeNAME(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool IsValidNameId(uint16_t nameID, bool addIfMissing = false); + + private: std::vector names; std::vector lang_tags; + std::unordered_set name_ids; }; } // namespace ots diff --git a/gfx/ots/src/os2.cc b/gfx/ots/src/os2.cc index fd5cdd1d6..5376a1dbb 100644 --- a/gfx/ots/src/os2.cc +++ b/gfx/ots/src/os2.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,77 +10,73 @@ // 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) { +bool OpenTypeOS2::Parse(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; + if (!table.ReadU16(&this->table.version) || + !table.ReadS16(&this->table.avg_char_width) || + !table.ReadU16(&this->table.weight_class) || + !table.ReadU16(&this->table.width_class) || + !table.ReadU16(&this->table.type) || + !table.ReadS16(&this->table.subscript_x_size) || + !table.ReadS16(&this->table.subscript_y_size) || + !table.ReadS16(&this->table.subscript_x_offset) || + !table.ReadS16(&this->table.subscript_y_offset) || + !table.ReadS16(&this->table.superscript_x_size) || + !table.ReadS16(&this->table.superscript_y_size) || + !table.ReadS16(&this->table.superscript_x_offset) || + !table.ReadS16(&this->table.superscript_y_offset) || + !table.ReadS16(&this->table.strikeout_size) || + !table.ReadS16(&this->table.strikeout_position) || + !table.ReadS16(&this->table.family_class)) { + return Error("Error reading basic table elements"); + } + + if (this->table.version > 5) { + return Error("Unsupported table version: %u", this->table.version); + } + + if (this->table.weight_class < 1) { + Warning("Bad usWeightClass: %u, changing it to %d", + this->table.weight_class, 1); + this->table.weight_class = 1; + } else if (this->table.weight_class > 1000) { + Warning("Bad usWeightClass: %u, changing it to %d", + this->table.weight_class, 1000); + this->table.weight_class = 1000; + } + + if (this->table.width_class < 1) { + Warning("Bad usWidthClass: %u, changing it to %d", + this->table.width_class, 1); + this->table.width_class = 1; + } else if (this->table.width_class > 9) { + Warning("Bad usWidthClass: %u, changing it to %d", + this->table.width_class, 9); + this->table.width_class = 9; } // lowest 3 bits of fsType are exclusive. - if (os2->type & 0x2) { + if (this->table.type & 0x2) { // mask bits 2 & 3. - os2->type &= 0xfff3u; - } else if (os2->type & 0x4) { + this->table.type &= 0xfff3u; + } else if (this->table.type & 0x4) { // mask bits 1 & 3. - os2->type &= 0xfff4u; - } else if (os2->type & 0x8) { + this->table.type &= 0xfff4u; + } else if (this->table.type & 0x8) { // mask bits 1 & 2. - os2->type &= 0xfff9u; + this->table.type &= 0xfff9u; } // mask reserved bits. use only 0..3, 8, 9 bits. - os2->type &= 0x30f; + this->table.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; \ +#define SET_TO_ZERO(a, b) \ + if (this->table.b < 0) { \ + Warning("Bad " a ": %d, setting it to zero", this->table.b); \ + this->table.b = 0; \ } SET_TO_ZERO("ySubscriptXSize", subscript_x_size); @@ -90,7 +86,7 @@ bool ots_os2_parse(Font *font, const uint8_t *data, size_t length) { SET_TO_ZERO("yStrikeoutSize", strikeout_size); #undef SET_TO_ZERO - static std::string panose_strings[10] = { + static const char* panose_strings[10] = { "bFamilyType", "bSerifStyle", "bWeight", @@ -103,234 +99,222 @@ bool ots_os2_parse(Font *font, const uint8_t *data, size_t length) { "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.ReadU8(&this->table.panose[i])) { + return Error("Failed to read PANOSE %s", panose_strings[i]); } } - 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 (!table.ReadU32(&this->table.unicode_range_1) || + !table.ReadU32(&this->table.unicode_range_2) || + !table.ReadU32(&this->table.unicode_range_3) || + !table.ReadU32(&this->table.unicode_range_4) || + !table.ReadU32(&this->table.vendor_id) || + !table.ReadU16(&this->table.selection) || + !table.ReadU16(&this->table.first_char_index) || + !table.ReadU16(&this->table.last_char_index) || + !table.ReadS16(&this->table.typo_ascender) || + !table.ReadS16(&this->table.typo_descender) || + !table.ReadS16(&this->table.typo_linegap) || + !table.ReadU16(&this->table.win_ascent) || + !table.ReadU16(&this->table.win_descent)) { + return Error("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; + if (this->table.selection & 0x40) { + this->table.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; + OpenTypeHEAD *head = static_cast( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); + + if ((this->table.selection & 0x1) && + head && !(head->mac_style & 0x2)) { + Warning("Adjusting head.macStyle (italic) to match fsSelection"); + head->mac_style |= 0x2; } - if ((os2->selection & 0x2) && - !(font->head->mac_style & 0x4)) { - OTS_WARNING("adjusting Mac style (underscore)"); - font->head->mac_style |= 0x4; + if ((this->table.selection & 0x2) && + head && !(head->mac_style & 0x4)) { + Warning("Adjusting head.macStyle (underscore) to match fsSelection"); + 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 ((this->table.selection & 0x40) && + head && (head->mac_style & 0x3)) { + Warning("Adjusting head.macStyle (regular) to match fsSelection"); + head->mac_style &= 0xfffcu; } - if ((os2->version < 4) && - (os2->selection & 0x300)) { + if ((this->table.version < 4) && + (this->table.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); + Warning("fsSelection bits 8 and 9 must be unset for table version %d", + this->table.version); } // mask reserved bits. use only 0..9 bits. - os2->selection &= 0x3ff; + this->table.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 (this->table.first_char_index > this->table.last_char_index) { + Warning("usFirstCharIndex %d > usLastCharIndex %d", + this->table.first_char_index, this->table.last_char_index); + this->table.first_char_index = this->table.last_char_index; } - if (os2->typo_linegap < 0) { - OTS_WARNING("bad linegap: %d", os2->typo_linegap); - os2->typo_linegap = 0; + if (this->table.typo_linegap < 0) { + Warning("Bad sTypoLineGap, setting it to 0: %d", this->table.typo_linegap); + this->table.typo_linegap = 0; } - if (os2->version < 1) { + if (this->table.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); + if (length < offsetof(OS2Data, code_page_range_2)) { + Warning("Bad version number, setting it to 0: %u", this->table.version); // Some fonts (e.g., kredit1.ttf and quinquef.ttf) have weird version // numbers. Fix them. - os2->version = 0; + this->table.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 (!table.ReadU32(&this->table.code_page_range_1) || + !table.ReadU32(&this->table.code_page_range_2)) { + return Error("Failed to read ulCodePageRange1 or ulCodePageRange2"); } - if (os2->version < 2) { + if (this->table.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); + if (length < offsetof(OS2Data, max_context)) { + Warning("Bad version number, setting it to 1: %u", this->table.version); // some Japanese fonts (e.g., mona.ttf) have weird version number. // fix them. - os2->version = 1; + this->table.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 (!table.ReadS16(&this->table.x_height) || + !table.ReadS16(&this->table.cap_height) || + !table.ReadU16(&this->table.default_char) || + !table.ReadU16(&this->table.break_char) || + !table.ReadU16(&this->table.max_context)) { + return Error("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 (this->table.x_height < 0) { + Warning("Bad sxHeight settig it to 0: %d", this->table.x_height); + this->table.x_height = 0; } - if (os2->cap_height < 0) { - OTS_WARNING("bad cap_height: %d", os2->cap_height); - os2->cap_height = 0; + if (this->table.cap_height < 0) { + Warning("Bad sCapHeight setting it to 0: %d", this->table.cap_height); + this->table.cap_height = 0; } - if (os2->version < 5) { + if (this->table.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 (!table.ReadU16(&this->table.lower_optical_pointsize) || + !table.ReadU16(&this->table.upper_optical_pointsize)) { + return Error("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 (this->table.lower_optical_pointsize > 0xFFFE) { + Warning("usLowerOpticalPointSize is bigger than 0xFFFE: %d", + this->table.lower_optical_pointsize); + this->table.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; + if (this->table.upper_optical_pointsize < 2) { + Warning("usUpperOpticalPointSize is lower than 2: %d", + this->table.upper_optical_pointsize); + this->table.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"); +bool OpenTypeOS2::Serialize(OTSStream *out) { + if (!out->WriteU16(this->table.version) || + !out->WriteS16(this->table.avg_char_width) || + !out->WriteU16(this->table.weight_class) || + !out->WriteU16(this->table.width_class) || + !out->WriteU16(this->table.type) || + !out->WriteS16(this->table.subscript_x_size) || + !out->WriteS16(this->table.subscript_y_size) || + !out->WriteS16(this->table.subscript_x_offset) || + !out->WriteS16(this->table.subscript_y_offset) || + !out->WriteS16(this->table.superscript_x_size) || + !out->WriteS16(this->table.superscript_y_size) || + !out->WriteS16(this->table.superscript_x_offset) || + !out->WriteS16(this->table.superscript_y_offset) || + !out->WriteS16(this->table.strikeout_size) || + !out->WriteS16(this->table.strikeout_position) || + !out->WriteS16(this->table.family_class)) { + return Error("Failed to write basic table data"); } 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->Write(&this->table.panose[i], 1)) { + return Error("Failed to write PANOSE data"); } } - 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 (!out->WriteU32(this->table.unicode_range_1) || + !out->WriteU32(this->table.unicode_range_2) || + !out->WriteU32(this->table.unicode_range_3) || + !out->WriteU32(this->table.unicode_range_4) || + !out->WriteU32(this->table.vendor_id) || + !out->WriteU16(this->table.selection) || + !out->WriteU16(this->table.first_char_index) || + !out->WriteU16(this->table.last_char_index) || + !out->WriteS16(this->table.typo_ascender) || + !out->WriteS16(this->table.typo_descender) || + !out->WriteS16(this->table.typo_linegap) || + !out->WriteU16(this->table.win_ascent) || + !out->WriteU16(this->table.win_descent)) { + return Error("Failed to write version 1-specific fields"); } - if (os2->version < 1) { + if (this->table.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 (!out->WriteU32(this->table.code_page_range_1) || + !out->WriteU32(this->table.code_page_range_2)) { + return Error("Failed to write codepage ranges"); } - if (os2->version < 2) { + if (this->table.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 (!out->WriteS16(this->table.x_height) || + !out->WriteS16(this->table.cap_height) || + !out->WriteU16(this->table.default_char) || + !out->WriteU16(this->table.break_char) || + !out->WriteU16(this->table.max_context)) { + return Error("Failed to write version 2-specific fields"); } - if (os2->version < 5) { + if (this->table.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"); + if (!out->WriteU16(this->table.lower_optical_pointsize) || + !out->WriteU16(this->table.upper_optical_pointsize)) { + return Error("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 index 01511c5dc..b3f1bad9b 100644 --- a/gfx/ots/src/os2.h +++ b/gfx/ots/src/os2.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,7 +9,7 @@ namespace ots { -struct OpenTypeOS2 { +struct OS2Data { uint16_t version; int16_t avg_char_width; uint16_t weight_class; @@ -51,6 +51,17 @@ struct OpenTypeOS2 { uint16_t upper_optical_pointsize; }; +class OpenTypeOS2 : public Table { + public: + explicit OpenTypeOS2(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + OS2Data table; +}; + } // namespace ots #endif // OTS_OS2_H_ diff --git a/gfx/ots/src/ots.cc b/gfx/ots/src/ots.cc index 15794475a..38d97a9ed 100644 --- a/gfx/ots/src/ots.cc +++ b/gfx/ots/src/ots.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -14,25 +14,75 @@ #include #include -#include "woff2_dec.h" +#include // The OpenType Font File -// http://www.microsoft.com/typography/otspec/cmap.htm +// http://www.microsoft.com/typography/otspec/otff.htm + +#include "avar.h" +#include "cff.h" +#include "cmap.h" +#include "cvar.h" +#include "cvt.h" +#include "fpgm.h" +#include "fvar.h" +#include "gasp.h" +#include "gdef.h" +#include "glyf.h" +#include "gpos.h" +#include "gsub.h" +#include "gvar.h" +#include "hdmx.h" +#include "head.h" +#include "hhea.h" +#include "hmtx.h" +#include "hvar.h" +#include "kern.h" +#include "loca.h" +#include "ltsh.h" +#include "math_.h" +#include "maxp.h" +#include "mvar.h" +#include "name.h" +#include "os2.h" +#include "ots.h" +#include "post.h" +#include "prep.h" +#include "stat.h" +#include "vdmx.h" +#include "vhea.h" +#include "vmtx.h" +#include "vorg.h" +#include "vvar.h" + +// Graphite tables +#ifdef OTS_GRAPHITE +#include "feat.h" +#include "glat.h" +#include "gloc.h" +#include "sile.h" +#include "silf.h" +#include "sill.h" +#endif -namespace { +namespace ots { -// 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 Arena { + public: + ~Arena() { + for (auto& hunk : hunks_) { + delete[] hunk; + } + } + uint8_t* Allocate(size_t length) { + uint8_t* p = new uint8_t[length]; + hunks_.push_back(p); + return p; + } -struct OpenTypeTable { - uint32_t tag; - uint32_t chksum; - uint32_t offset; - uint32_t length; - uint32_t uncompressed_length; + private: + std::vector hunks_; }; bool CheckTag(uint32_t tag_value) { @@ -46,99 +96,83 @@ bool CheckTag(uint32_t tag_value) { return true; } -struct Arena { - public: - ~Arena() { - for (std::vector::iterator - i = hunks_.begin(); i != hunks_.end(); ++i) { - delete[] *i; - } - } +}; // namespace ots - uint8_t* Allocate(size_t length) { - uint8_t* p = new uint8_t[length]; - hunks_.push_back(p); - return p; - } +namespace { + +#define OTS_MSG_TAG_(level,otf_,msg_,tag_) \ + (OTS_MESSAGE_(level,otf_,"%c%c%c%c: %s", OTS_UNTAG(tag_), msg_), false) + +// Generate a message with or without a table tag, when 'header' is the FontFile pointer +#define OTS_FAILURE_MSG_TAG(msg_,tag_) OTS_MSG_TAG_(0, header, msg_, tag_) +#define OTS_FAILURE_MSG_HDR(...) OTS_FAILURE_MSG_(header, __VA_ARGS__) +#define OTS_WARNING_MSG_HDR(...) OTS_WARNING_MSG_(header, __VA_ARGS__) - private: - std::vector 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 }, +} supported_tables[] = { + { OTS_TAG_MAXP, true }, + { OTS_TAG_HEAD, true }, + { OTS_TAG_OS2, true }, + { OTS_TAG_CMAP, true }, + { OTS_TAG_HHEA, true }, + { OTS_TAG_HMTX, true }, + { OTS_TAG_NAME, true }, + { OTS_TAG_POST, true }, + { OTS_TAG_LOCA, false }, + { OTS_TAG_GLYF, false }, + { OTS_TAG_CFF, false }, + { OTS_TAG_VDMX, false }, + { OTS_TAG_HDMX, false }, + { OTS_TAG_GASP, false }, + { OTS_TAG_CVT, false }, + { OTS_TAG_FPGM, false }, + { OTS_TAG_PREP, false }, + { OTS_TAG_LTSH, false }, + { OTS_TAG_VORG, false }, + { OTS_TAG_KERN, false }, + // We need to parse fvar table before other tables that may need to know + // the number of variation axes (if any) + { OTS_TAG_FVAR, false }, + { OTS_TAG_AVAR, false }, + { OTS_TAG_CVAR, false }, + { OTS_TAG_GVAR, false }, + { OTS_TAG_HVAR, false }, + { OTS_TAG_MVAR, false }, + { OTS_TAG_STAT, false }, + { OTS_TAG_VVAR, false }, + { OTS_TAG_CFF2, 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 }, + { OTS_TAG_GDEF, false }, + { OTS_TAG_GPOS, false }, + { OTS_TAG_GSUB, false }, + { OTS_TAG_VHEA, false }, + { OTS_TAG_VMTX, false }, + { OTS_TAG_MATH, false }, + // Graphite tables +#ifdef OTS_GRAPHITE + { OTS_TAG_GLOC, false }, + { OTS_TAG_GLAT, false }, + { OTS_TAG_FEAT, false }, + { OTS_TAG_SILF, false }, + { OTS_TAG_SILE, false }, + { OTS_TAG_SILL, false }, +#endif + { 0, false }, }; -bool ProcessGeneric(ots::OpenTypeFile *header, +bool ProcessGeneric(ots::FontFile *header, ots::Font *font, uint32_t signature, ots::OTSStream *output, const uint8_t *data, size_t length, - const std::vector& tables, + const std::vector& tables, ots::Buffer& file); -bool ProcessTTF(ots::OpenTypeFile *header, +bool ProcessTTF(ots::FontFile *header, ots::Font *font, ots::OTSStream *output, const uint8_t *data, size_t length, uint32_t offset = 0) { @@ -202,10 +236,10 @@ bool ProcessTTF(ots::OpenTypeFile *header, } // Next up is the list of tables. - std::vector tables; + std::vector tables; for (unsigned i = 0; i < font->num_tables; ++i) { - OpenTypeTable table; + ots::TableEntry table; if (!file.ReadU32(&table.tag) || !file.ReadU32(&table.chksum) || !file.ReadU32(&table.offset) || @@ -221,7 +255,7 @@ bool ProcessTTF(ots::OpenTypeFile *header, tables, file); } -bool ProcessTTC(ots::OpenTypeFile *header, +bool ProcessTTC(ots::FontFile *header, ots::OTSStream *output, const uint8_t *data, size_t length, @@ -308,7 +342,7 @@ bool ProcessTTC(ots::OpenTypeFile *header, } } -bool ProcessWOFF(ots::OpenTypeFile *header, +bool ProcessWOFF(ots::FontFile *header, ots::Font *font, ots::OTSStream *output, const uint8_t *data, size_t length) { ots::Buffer file(data, length); @@ -388,14 +422,14 @@ bool ProcessWOFF(ots::OpenTypeFile *header, } // Next up is the list of tables. - std::vector tables; + std::vector 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; + ots::TableEntry table; if (!file.ReadU32(&table.tag) || !file.ReadU32(&table.offset) || !file.ReadU32(&table.length) || @@ -463,13 +497,17 @@ bool ProcessWOFF(ots::OpenTypeFile *header, return ProcessGeneric(header, font, woff_tag, output, data, length, tables, file); } -bool ProcessWOFF2(ots::OpenTypeFile *header, +bool ProcessWOFF2(ots::FontFile *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 < length) { + return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is less than compressed size"); + } + if (decompressed_size == 0) { return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is set to 0"); } @@ -493,16 +531,16 @@ bool ProcessWOFF2(ots::OpenTypeFile *header, } } -ots::TableAction GetTableAction(ots::OpenTypeFile *header, uint32_t tag) { +ots::TableAction GetTableAction(const ots::FontFile *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 (supported_tables[i].tag == 0) break; - if (table_parsers[i].tag == tag) { + if (supported_tables[i].tag == tag) { action = ots::TABLE_ACTION_SANITIZE; break; } @@ -514,14 +552,14 @@ ots::TableAction GetTableAction(ots::OpenTypeFile *header, uint32_t tag) { } bool GetTableData(const uint8_t *data, - const OpenTypeTable& table, - Arena *arena, + const ots::TableEntry& table, + ots::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); + *table_data = arena.Allocate(*table_length); uLongf dest_len = *table_length; int r = uncompress((Bytef*) *table_data, &dest_len, data + table.offset, table.length); @@ -537,12 +575,12 @@ bool GetTableData(const uint8_t *data, return true; } -bool ProcessGeneric(ots::OpenTypeFile *header, +bool ProcessGeneric(ots::FontFile *header, ots::Font *font, uint32_t signature, ots::OTSStream *output, const uint8_t *data, size_t length, - const std::vector& tables, + const std::vector& tables, ots::Buffer& file) { const size_t data_offset = file.offset(); @@ -560,8 +598,8 @@ bool ProcessGeneric(ots::OpenTypeFile *header, } // 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); + if (!ots::CheckTag(tables[i].tag)) { + OTS_WARNING_MSG_HDR("Invalid table tag: 0x%X", tables[i].tag); } // tables must be 4-byte aligned @@ -616,11 +654,6 @@ bool ProcessGeneric(ots::OpenTypeFile *header, return OTS_FAILURE_MSG_HDR("uncompressed sum exceeds 30MB"); } - std::map 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 > overlap_checker; for (unsigned i = 0; i < font->num_tables; ++i) { @@ -639,80 +672,66 @@ bool ProcessGeneric(ots::OpenTypeFile *header, } } - Arena arena; + std::map table_map; + for (unsigned i = 0; i < font->num_tables; ++i) { + table_map[tables[i].tag] = tables[i]; + } + ots::Arena arena; + // Parse known tables first as we need to parse them in specific order. for (unsigned i = 0; ; ++i) { - if (table_parsers[i].parse == NULL) break; - - uint32_t tag = table_parsers[i].tag; - const std::map::const_iterator it = table_map.find(tag); + if (supported_tables[i].tag == 0) break; - 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); + uint32_t tag = supported_tables[i].tag; + const auto &it = table_map.find(tag); + if (it == table_map.cend()) { + if (supported_tables[i].required) { + return OTS_FAILURE_MSG_TAG("missing required table", 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); + } else { + if (!font->ParseTable(it->second, data, arena)) { + return OTS_FAILURE_MSG_TAG("Failed to parse table", tag); } + } + } - if (action == ots::TABLE_ACTION_SANITIZE && - !table_parsers[i].parse(font, table_data, table_length)) { - return OTS_FAILURE(); + // Then parse any tables left. + for (const auto &table_entry : tables) { + if (!font->GetTable(table_entry.tag)) { + if (!font->ParseTable(table_entry, data, arena)) { + return OTS_FAILURE_MSG_TAG("Failed to parse table", table_entry.tag); } - } else if (action == ots::TABLE_ACTION_SANITIZE) { - table_parsers[i].reuse(font, ot->second.first); } } - if (font->cff) { + if (font->GetTable(OTS_TAG_CFF) || font->GetTable(OTS_TAG_CFF2)) { // 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) { + if (font->GetTable(OTS_TAG_GLYF) || font->GetTable(OTS_TAG_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) { + if (!font->GetTable(OTS_TAG_GLYF) || !font->GetTable(OTS_TAG_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'))) { + // + // We don't sanitize bitmap tables, but don’t reject bitmap-only fonts if + // we are asked to pass them thru. + // Also don’t reject if we are asked to pass glyf/loca thru. + if (!font->GetTable(OTS_TAG('C','B','D','T')) && + !font->GetTable(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::const_iterator it = table_map.begin(); - it != table_map.end(); ++it) { - ots::TableAction action = GetTableAction(header, it->first); - if (action == ots::TABLE_ACTION_PASSTHRU) { + for (const auto &it : table_map) { + ots::Table *table = font->GetTable(it.first); + if (table != NULL && table->ShouldSerialize()) 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; @@ -738,85 +757,49 @@ bool ProcessGeneric(ots::OpenTypeFile *header, return OTS_FAILURE_MSG_HDR("error writing output"); } - std::vector out_tables; + std::vector out_tables; size_t head_table_offset = 0; - for (std::map::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; + for (const auto &it : table_map) { + uint32_t input_offset = it.second.offset; + const auto &ot = header->table_entries.find(input_offset); + if (ot != header->table_entries.end()) { + ots::TableEntry out = ot->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; + ots::TableEntry 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) { + ots::Table *table = font->GetTable(out.tag); + if (table != NULL && table->ShouldSerialize()) { 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"); + if (!table->Serialize(output)) { + return OTS_FAILURE_MSG_TAG("Failed to serialize table", out.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"); + return OTS_FAILURE_MSG_TAG("Table is empty or have -ve size", out.tag); } 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"); + return OTS_FAILURE_MSG_TAG("Failed to pad table to 4 bytes", out.tag); } 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; - } - } + header->table_entries[input_offset] = out; } } } @@ -869,6 +852,225 @@ bool ProcessGeneric(ots::OpenTypeFile *header, namespace ots { +FontFile::~FontFile() { + for (const auto& it : tables) { + delete it.second; + } + tables.clear(); +} + +bool Font::ParseTable(const TableEntry& table_entry, const uint8_t* data, + Arena &arena) { + uint32_t tag = table_entry.tag; + TableAction action = GetTableAction(file, tag); + if (action == TABLE_ACTION_DROP) { + return true; + } + + const auto &it = file->tables.find(table_entry); + if (it != file->tables.end()) { + m_tables[tag] = it->second; + return true; + } + + Table *table = NULL; + bool ret = false; + + if (action == TABLE_ACTION_PASSTHRU) { + table = new TablePassthru(this, tag); + } else { + switch (tag) { + case OTS_TAG_AVAR: table = new OpenTypeAVAR(this, tag); break; + case OTS_TAG_CFF: table = new OpenTypeCFF(this, tag); break; + case OTS_TAG_CFF2: table = new OpenTypeCFF2(this, tag); break; + case OTS_TAG_CMAP: table = new OpenTypeCMAP(this, tag); break; + case OTS_TAG_CVAR: table = new OpenTypeCVAR(this, tag); break; + case OTS_TAG_CVT: table = new OpenTypeCVT(this, tag); break; + case OTS_TAG_FPGM: table = new OpenTypeFPGM(this, tag); break; + case OTS_TAG_FVAR: table = new OpenTypeFVAR(this, tag); break; + case OTS_TAG_GASP: table = new OpenTypeGASP(this, tag); break; + case OTS_TAG_GDEF: table = new OpenTypeGDEF(this, tag); break; + case OTS_TAG_GLYF: table = new OpenTypeGLYF(this, tag); break; + case OTS_TAG_GPOS: table = new OpenTypeGPOS(this, tag); break; + case OTS_TAG_GSUB: table = new OpenTypeGSUB(this, tag); break; + case OTS_TAG_GVAR: table = new OpenTypeGVAR(this, tag); break; + case OTS_TAG_HDMX: table = new OpenTypeHDMX(this, tag); break; + case OTS_TAG_HEAD: table = new OpenTypeHEAD(this, tag); break; + case OTS_TAG_HHEA: table = new OpenTypeHHEA(this, tag); break; + case OTS_TAG_HMTX: table = new OpenTypeHMTX(this, tag); break; + case OTS_TAG_HVAR: table = new OpenTypeHVAR(this, tag); break; + case OTS_TAG_KERN: table = new OpenTypeKERN(this, tag); break; + case OTS_TAG_LOCA: table = new OpenTypeLOCA(this, tag); break; + case OTS_TAG_LTSH: table = new OpenTypeLTSH(this, tag); break; + case OTS_TAG_MATH: table = new OpenTypeMATH(this, tag); break; + case OTS_TAG_MAXP: table = new OpenTypeMAXP(this, tag); break; + case OTS_TAG_MVAR: table = new OpenTypeMVAR(this, tag); break; + case OTS_TAG_NAME: table = new OpenTypeNAME(this, tag); break; + case OTS_TAG_OS2: table = new OpenTypeOS2(this, tag); break; + case OTS_TAG_POST: table = new OpenTypePOST(this, tag); break; + case OTS_TAG_PREP: table = new OpenTypePREP(this, tag); break; + case OTS_TAG_STAT: table = new OpenTypeSTAT(this, tag); break; + case OTS_TAG_VDMX: table = new OpenTypeVDMX(this, tag); break; + case OTS_TAG_VHEA: table = new OpenTypeVHEA(this, tag); break; + case OTS_TAG_VMTX: table = new OpenTypeVMTX(this, tag); break; + case OTS_TAG_VORG: table = new OpenTypeVORG(this, tag); break; + case OTS_TAG_VVAR: table = new OpenTypeVVAR(this, tag); break; + // Graphite tables +#ifdef OTS_GRAPHITE + case OTS_TAG_FEAT: table = new OpenTypeFEAT(this, tag); break; + case OTS_TAG_GLAT: table = new OpenTypeGLAT(this, tag); break; + case OTS_TAG_GLOC: table = new OpenTypeGLOC(this, tag); break; + case OTS_TAG_SILE: table = new OpenTypeSILE(this, tag); break; + case OTS_TAG_SILF: table = new OpenTypeSILF(this, tag); break; + case OTS_TAG_SILL: table = new OpenTypeSILL(this, tag); break; +#endif + default: break; + } + } + + if (table) { + const uint8_t* table_data; + size_t table_length; + + ret = GetTableData(data, table_entry, arena, &table_length, &table_data); + if (ret) { + // FIXME: Parsing some tables will fail if the table is not added to + // m_tables first. + m_tables[tag] = table; + ret = table->Parse(table_data, table_length); + if (ret) + file->tables[table_entry] = table; + else + m_tables.erase(tag); + } + } + + if (!ret) + delete table; + + return ret; +} + +Table* Font::GetTable(uint32_t tag) const { + const auto &it = m_tables.find(tag); + if (it != m_tables.end()) + return it->second; + return NULL; +} + +Table* Font::GetTypedTable(uint32_t tag) const { + Table* t = GetTable(tag); + if (t && t->Type() == tag) + return t; + return NULL; +} + +void Font::DropGraphite() { + file->context->Message(0, "Dropping all Graphite tables"); + for (const std::pair entry : m_tables) { + if (entry.first == OTS_TAG_FEAT || + entry.first == OTS_TAG_GLAT || + entry.first == OTS_TAG_GLOC || + entry.first == OTS_TAG_SILE || + entry.first == OTS_TAG_SILF || + entry.first == OTS_TAG_SILL) { + entry.second->Drop("Discarding Graphite table"); + } + } + dropped_graphite = true; +} + +void Font::DropVariations() { + file->context->Message(0, "Dropping all Variation tables"); + for (const std::pair entry : m_tables) { + if (entry.first == OTS_TAG_AVAR || + entry.first == OTS_TAG_CVAR || + entry.first == OTS_TAG_FVAR || + entry.first == OTS_TAG_GVAR || + entry.first == OTS_TAG_HVAR || + entry.first == OTS_TAG_MVAR || + entry.first == OTS_TAG_STAT || + entry.first == OTS_TAG_VVAR) { + entry.second->Drop("Discarding Variations table"); + } + } + dropped_variations = true; +} + +bool Table::ShouldSerialize() { + return m_shouldSerialize; +} + +void Table::Message(int level, const char *format, va_list va) { + char msg[206] = { OTS_UNTAG(m_tag), ':', ' ' }; + std::vsnprintf(msg + 6, 200, format, va); + m_font->file->context->Message(level, msg); +} + +bool Table::Error(const char *format, ...) { + va_list va; + va_start(va, format); + Message(0, format, va); + va_end(va); + + return false; +} + +bool Table::Warning(const char *format, ...) { + va_list va; + va_start(va, format); + Message(1, format, va); + va_end(va); + + return true; +} + +bool Table::Drop(const char *format, ...) { + m_shouldSerialize = false; + + va_list va; + va_start(va, format); + Message(0, format, va); + m_font->file->context->Message(0, "Table discarded"); + va_end(va); + + return true; +} + +bool Table::DropGraphite(const char *format, ...) { + va_list va; + va_start(va, format); + Message(0, format, va); + va_end(va); + + m_font->DropGraphite(); + return true; +} + +bool Table::DropVariations(const char *format, ...) { + va_list va; + va_start(va, format); + Message(0, format, va); + va_end(va); + + m_font->DropVariations(); + return true; +} + +bool TablePassthru::Parse(const uint8_t *data, size_t length) { + m_data = data; + m_length = length; + return true; +} + +bool TablePassthru::Serialize(OTSStream *out) { + if (!out->Write(m_data, m_length)) { + return Error("Failed to write table"); + } + + return true; +} + bool IsValidVersionTag(uint32_t tag) { return tag == 0x000010000 || // OpenType fonts with CFF data have 'OTTO' tag. @@ -882,7 +1084,7 @@ bool OTSContext::Process(OTSStream *output, const uint8_t *data, size_t length, uint32_t index) { - OpenTypeFile header; + FontFile header; Font font(&header); header.context = this; diff --git a/gfx/ots/src/ots.h b/gfx/ots/src/ots.h index 2d13f8d6d..4d2be1689 100644 --- a/gfx/ots/src/ots.h +++ b/gfx/ots/src/ots.h @@ -1,10 +1,14 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS 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_ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include #include #include @@ -54,13 +58,9 @@ namespace ots { #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. +// expected to be the current FontFile 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__) @@ -112,7 +112,7 @@ class Buffer { return OTS_FAILURE(); } std::memcpy(value, buffer_ + offset_, sizeof(uint16_t)); - *value = ntohs(*value); + *value = ots_ntohs(*value); offset_ += 2; return true; } @@ -137,7 +137,7 @@ class Buffer { return OTS_FAILURE(); } std::memcpy(value, buffer_ + offset_, sizeof(uint32_t)); - *value = ntohl(*value); + *value = ots_ntohl(*value); offset_ += 4; return true; } @@ -184,111 +184,175 @@ template T Round2(T value) { return (value + 1) & ~1; } +// Check that a tag consists entirely of printable ASCII characters +bool CheckTag(uint32_t tag_value); + 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 +#define OTS_TAG_CFF OTS_TAG('C','F','F',' ') +#define OTS_TAG_CFF2 OTS_TAG('C','F','F','2') +#define OTS_TAG_CMAP OTS_TAG('c','m','a','p') +#define OTS_TAG_CVT OTS_TAG('c','v','t',' ') +#define OTS_TAG_FEAT OTS_TAG('F','e','a','t') +#define OTS_TAG_FPGM OTS_TAG('f','p','g','m') +#define OTS_TAG_GASP OTS_TAG('g','a','s','p') +#define OTS_TAG_GDEF OTS_TAG('G','D','E','F') +#define OTS_TAG_GLAT OTS_TAG('G','l','a','t') +#define OTS_TAG_GLOC OTS_TAG('G','l','o','c') +#define OTS_TAG_GLYF OTS_TAG('g','l','y','f') +#define OTS_TAG_GPOS OTS_TAG('G','P','O','S') +#define OTS_TAG_GSUB OTS_TAG('G','S','U','B') +#define OTS_TAG_HDMX OTS_TAG('h','d','m','x') +#define OTS_TAG_HEAD OTS_TAG('h','e','a','d') +#define OTS_TAG_HHEA OTS_TAG('h','h','e','a') +#define OTS_TAG_HMTX OTS_TAG('h','m','t','x') +#define OTS_TAG_KERN OTS_TAG('k','e','r','n') +#define OTS_TAG_LOCA OTS_TAG('l','o','c','a') +#define OTS_TAG_LTSH OTS_TAG('L','T','S','H') +#define OTS_TAG_MATH OTS_TAG('M','A','T','H') +#define OTS_TAG_MAXP OTS_TAG('m','a','x','p') +#define OTS_TAG_NAME OTS_TAG('n','a','m','e') +#define OTS_TAG_OS2 OTS_TAG('O','S','/','2') +#define OTS_TAG_POST OTS_TAG('p','o','s','t') +#define OTS_TAG_PREP OTS_TAG('p','r','e','p') +#define OTS_TAG_SILE OTS_TAG('S','i','l','e') +#define OTS_TAG_SILF OTS_TAG('S','i','l','f') +#define OTS_TAG_SILL OTS_TAG('S','i','l','l') +#define OTS_TAG_VDMX OTS_TAG('V','D','M','X') +#define OTS_TAG_VHEA OTS_TAG('v','h','e','a') +#define OTS_TAG_VMTX OTS_TAG('v','m','t','x') +#define OTS_TAG_VORG OTS_TAG('V','O','R','G') + +#define OTS_TAG_AVAR OTS_TAG('a','v','a','r') +#define OTS_TAG_CVAR OTS_TAG('c','v','a','r') +#define OTS_TAG_FVAR OTS_TAG('f','v','a','r') +#define OTS_TAG_GVAR OTS_TAG('g','v','a','r') +#define OTS_TAG_HVAR OTS_TAG('H','V','A','R') +#define OTS_TAG_MVAR OTS_TAG('M','V','A','R') +#define OTS_TAG_VVAR OTS_TAG('V','V','A','R') +#define OTS_TAG_STAT OTS_TAG('S','T','A','T') struct Font; -struct OpenTypeFile; +struct FontFile; +struct TableEntry; +struct Arena; + +class Table { + public: + explicit Table(Font *font, uint32_t tag, uint32_t type) + : m_tag(tag), + m_type(type), + m_font(font), + m_shouldSerialize(true) { + } + + virtual ~Table() { } -#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 + virtual bool Parse(const uint8_t *data, size_t length) = 0; + virtual bool Serialize(OTSStream *out) = 0; + virtual bool ShouldSerialize(); + + // Return the tag (table type) this Table was parsed as, to support + // "poor man's RTTI" so that we know if we can safely down-cast to + // a specific Table subclass. The m_type field is initialized to the + // appropriate tag when a subclass is constructed, or to zero for + // TablePassthru (indicating unparsed data). + uint32_t Type() { return m_type; } + + Font* GetFont() { return m_font; } + + bool Error(const char *format, ...); + bool Warning(const char *format, ...); + bool Drop(const char *format, ...); + bool DropGraphite(const char *format, ...); + bool DropVariations(const char *format, ...); + + private: + void Message(int level, const char *format, va_list va); + + uint32_t m_tag; + uint32_t m_type; + Font *m_font; + bool m_shouldSerialize; +}; + +class TablePassthru : public Table { + public: + explicit TablePassthru(Font *font, uint32_t tag) + : Table(font, tag, 0), + m_data(NULL), + m_length(0) { + } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + private: + const uint8_t *m_data; + size_t m_length; +}; struct Font { - explicit Font(const OpenTypeFile *f) + explicit Font(FontFile *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 + range_shift(0), + dropped_graphite(false), + dropped_variations(false) { } - ~Font() { -#define F(name, capname) \ - if (!name##_reused) {\ - ots_##name##_free(this); \ - } - FOR_EACH_TABLE_TYPE -#undef F - } + bool ParseTable(const TableEntry& tableinfo, const uint8_t* data, + Arena &arena); + Table* GetTable(uint32_t tag) const; + + // This checks that the returned Table is actually of the correct subclass + // for |tag|, so it can safely be downcast to the corresponding OpenTypeXXXX; + // if not (i.e. if the table was treated as Passthru), it will return NULL. + Table* GetTypedTable(uint32_t tag) const; + + // Drop all Graphite tables and don't parse new ones. + void DropGraphite(); - const OpenTypeFile *file; + // Drop all Variations tables and don't parse new ones. + void DropVariations(); + + FontFile *file; uint32_t version; uint16_t num_tables; uint16_t search_range; uint16_t entry_selector; uint16_t range_shift; + bool dropped_graphite; + bool dropped_variations; -#define F(name, capname) \ - OpenType##capname *name; \ - bool name##_reused; -FOR_EACH_TABLE_TYPE -#undef F + private: + std::map m_tables; }; -struct OutputTable { +struct TableEntry { uint32_t tag; - size_t offset; - size_t length; + uint32_t offset; + uint32_t length; + uint32_t uncompressed_length; uint32_t chksum; - bool operator<(const OutputTable& other) const { + bool operator<(const TableEntry& other) const { return tag < other.tag; } }; -typedef std::map > TableMap; +struct FontFile { + ~FontFile(); -struct OpenTypeFile { OTSContext *context; - TableMap tables; + std::map tables; + std::map table_entries; }; } // namespace ots -#undef FOR_EACH_TABLE_TYPE - #endif // OTS_H_ diff --git a/gfx/ots/src/post.cc b/gfx/ots/src/post.cc index a110b2dea..704c32007 100644 --- a/gfx/ots/src/post.cc +++ b/gfx/ots/src/post.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,74 +9,73 @@ // 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) { +bool OpenTypePOST::Parse(const uint8_t *data, size_t length) { Buffer table(data, length); - OpenTypePOST *post = new OpenTypePOST; - font->post = post; + if (!table.ReadU32(&this->version)) { + return Error("Failed to read table version"); + } - 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 (this->version != 0x00010000 && + this->version != 0x00020000 && + this->version != 0x00030000) { + // 0x00025000 is deprecated. We don't accept it. + return Error("Unsupported table version 0x%x", this->version); } - if (post->underline_thickness < 0) { - post->underline_thickness = 1; + if (!table.ReadU32(&this->italic_angle) || + !table.ReadS16(&this->underline) || + !table.ReadS16(&this->underline_thickness) || + !table.ReadU32(&this->is_fixed_pitch) || + // We don't care about the memory usage fields. We'll set all these to + // zero when serialising + !table.Skip(16)) { + return Error("Failed to read table header"); } - if (post->version == 0x00010000) { - return true; - } else if (post->version == 0x00030000) { + if (this->underline_thickness < 0) { + this->underline_thickness = 1; + } + + if (this->version == 0x00010000 || this->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"); + return Error("Failed to read numberOfGlyphs"); } - if (!font->maxp) { - return OTS_FAILURE_MSG("No maxp table required by post table"); + OpenTypeMAXP* maxp = static_cast + (GetFont()->GetTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Missing required maxp 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"); + if (maxp->num_glyphs > 258) { + return Error("Can't have no glyphs in the post table if there are more " + "than 258 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; + this->version = 0x00010000; + return Warning("Table version is 1, but no glyph names are found"); } - if (num_glyphs != font->maxp->num_glyphs) { + if (num_glyphs != 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); + return Error("Bad number of glyphs: %d", num_glyphs); } - post->glyph_name_index.resize(num_glyphs); + this->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); + if (!table.ReadU16(&this->glyph_name_index[i])) { + return Error("Failed to read glyph name %d", i); } // Note: A strict interpretation of the specification requires name indexes // are less than 32768. This, however, excludes fonts like unifont.ttf @@ -93,101 +92,85 @@ bool ots_post_parse(Font *font, const uint8_t *data, size_t length) { 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); + return Error("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); + return Error("Bad string of length %d", string_length); } - post->names.push_back( + this->names.push_back( std::string(reinterpret_cast(strings + 1), string_length)); strings += 1 + string_length; } - const unsigned num_strings = post->names.size(); + const unsigned num_strings = this->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]; + unsigned offset = this->glyph_name_index[i]; if (offset < 258) { continue; } offset -= 258; if (offset >= num_strings) { - return OTS_FAILURE_MSG("Bad string index %d", offset); + return Error("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; - +bool OpenTypePOST::Serialize(OTSStream *out) { // 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 (GetFont()->GetTable(OTS_TAG_CFF) && this->version != 0x00030000) { + return Error("Only version supported for fonts with CFF table is 0x00030000" + " not 0x%x", this->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) || + if (!out->WriteU32(this->version) || + !out->WriteU32(this->italic_angle) || + !out->WriteS16(this->underline) || + !out->WriteS16(this->underline_thickness) || + !out->WriteU32(this->is_fixed_pitch) || !out->WriteU32(0) || !out->WriteU32(0) || !out->WriteU32(0) || !out->WriteU32(0)) { - return OTS_FAILURE_MSG("Failed to write post header"); + return Error("Failed to write post header"); } - if (post->version != 0x00020000) { + if (this->version != 0x00020000) { return true; // v1.0 and v3.0 does not have glyph names. } const uint16_t num_indexes = - static_cast(post->glyph_name_index.size()); - if (num_indexes != post->glyph_name_index.size() || + static_cast(this->glyph_name_index.size()); + if (num_indexes != this->glyph_name_index.size() || !out->WriteU16(num_indexes)) { - return OTS_FAILURE_MSG("Failed to write number of indices"); + return Error("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); + if (!out->WriteU16(this->glyph_name_index[i])) { + return Error("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]; + for (unsigned i = 0; i < this->names.size(); ++i) { + const std::string& s = this->names[i]; const uint8_t string_length = static_cast(s.size()); if (string_length != s.size() || !out->Write(&string_length, 1)) { - return OTS_FAILURE_MSG("Failed to write string %d", i); + return Error("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 Error("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 index f220d4fc7..c341e391c 100644 --- a/gfx/ots/src/post.h +++ b/gfx/ots/src/post.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,7 +13,15 @@ namespace ots { -struct OpenTypePOST { +class OpenTypePOST : public Table { + public: + explicit OpenTypePOST(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + +private: uint32_t version; uint32_t italic_angle; int16_t underline; diff --git a/gfx/ots/src/prep.cc b/gfx/ots/src/prep.cc index 1c9b45f91..943bb45b9 100644 --- a/gfx/ots/src/prep.cc +++ b/gfx/ots/src/prep.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,53 +7,37 @@ // 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) { +bool OpenTypePREP::Parse(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. + // almost all prep tables are less than 9k bytes. + return Error("Table length %ld > 120K", length); } if (!table.Skip(length)) { - return OTS_FAILURE_MSG("Failed to read table of length %ld", length); + return Error("Failed to read table of length %ld", length); } - prep->data = data; - prep->length = length; + this->m_data = data; + this->m_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"); +bool OpenTypePREP::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("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; +bool OpenTypePREP::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/prep.h b/gfx/ots/src/prep.h index 935ca111c..4d3eda2cd 100644 --- a/gfx/ots/src/prep.h +++ b/gfx/ots/src/prep.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,9 +9,18 @@ namespace ots { -struct OpenTypePREP { - const uint8_t *data; - uint32_t length; +class OpenTypePREP : public Table { + public: + explicit OpenTypePREP(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); + + private: + const uint8_t *m_data; + uint32_t m_length; }; } // namespace ots diff --git a/gfx/ots/src/sile.cc b/gfx/ots/src/sile.cc new file mode 100644 index 000000000..74405e56e --- /dev/null +++ b/gfx/ots/src/sile.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2009-2017 The OTS 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 "sile.h" + +namespace ots { + +bool OpenTypeSILE::Parse(const uint8_t* data, size_t length) { + if (GetFont()->dropped_graphite) { + return Drop("Skipping Graphite table"); + } + Buffer table(data, length); + + if (!table.ReadU32(&this->version) || this->version >> 16 != 1) { + return DropGraphite("Failed to read valid version"); + } + if (!table.ReadU32(&this->checksum)) { + return DropGraphite("Failed to read checksum"); + } + if (!table.ReadU32(&this->createTime[0]) || + !table.ReadU32(&this->createTime[1])) { + return DropGraphite("Failed to read createTime"); + } + if (!table.ReadU32(&this->modifyTime[0]) || + !table.ReadU32(&this->modifyTime[1])) { + return DropGraphite("Failed to read modifyTime"); + } + + if (!table.ReadU16(&this->fontNameLength)) { + return DropGraphite("Failed to read fontNameLength"); + } + //this->fontName.resize(this->fontNameLength); + for (unsigned i = 0; i < this->fontNameLength; ++i) { + this->fontName.emplace_back(); + if (!table.ReadU16(&this->fontName[i])) { + return DropGraphite("Failed to read fontName[%u]", i); + } + } + + if (!table.ReadU16(&this->fontFileLength)) { + return DropGraphite("Failed to read fontFileLength"); + } + //this->baseFile.resize(this->fontFileLength); + for (unsigned i = 0; i < this->fontFileLength; ++i) { + this->baseFile.emplace_back(); + if (!table.ReadU16(&this->baseFile[i])) { + return DropGraphite("Failed to read baseFile[%u]", i); + } + } + + if (table.remaining()) { + return Warning("%zu bytes unparsed", table.remaining()); + } + return true; +} + +bool OpenTypeSILE::Serialize(OTSStream* out) { + if (!out->WriteU32(this->version) || + !out->WriteU32(this->checksum) || + !out->WriteU32(this->createTime[0]) || + !out->WriteU32(this->createTime[1]) || + !out->WriteU32(this->modifyTime[0]) || + !out->WriteU32(this->modifyTime[1]) || + !out->WriteU16(this->fontNameLength) || + !SerializeParts(this->fontName, out) || + !out->WriteU16(this->fontFileLength) || + !SerializeParts(this->baseFile, out)) { + return Error("Failed to write table"); + } + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/sile.h b/gfx/ots/src/sile.h new file mode 100644 index 000000000..bdb00606f --- /dev/null +++ b/gfx/ots/src/sile.h @@ -0,0 +1,36 @@ +// Copyright (c) 2009-2017 The OTS 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_SILE_H_ +#define OTS_SILE_H_ + +#include "ots.h" +#include "graphite.h" + +#include + +namespace ots { + +class OpenTypeSILE : public Table { + public: + explicit OpenTypeSILE(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + uint32_t version; + uint32_t checksum; + uint32_t createTime[2]; + uint32_t modifyTime[2]; + uint16_t fontNameLength; + std::vector fontName; + uint16_t fontFileLength; + std::vector baseFile; +}; + +} // namespace ots + +#endif // OTS_SILE_H_ diff --git a/gfx/ots/src/silf.cc b/gfx/ots/src/silf.cc new file mode 100644 index 000000000..fa4e59e52 --- /dev/null +++ b/gfx/ots/src/silf.cc @@ -0,0 +1,977 @@ +// Copyright (c) 2009-2017 The OTS 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 "silf.h" + +#include "name.h" +#include "mozilla/Compression.h" +#include + +namespace ots { + +bool OpenTypeSILF::Parse(const uint8_t* data, size_t length, + bool prevent_decompression) { + if (GetFont()->dropped_graphite) { + return Drop("Skipping Graphite table"); + } + Buffer table(data, length); + + if (!table.ReadU32(&this->version)) { + return DropGraphite("Failed to read version"); + } + if (this->version >> 16 != 1 && + this->version >> 16 != 2 && + this->version >> 16 != 3 && + this->version >> 16 != 4 && + this->version >> 16 != 5) { + return DropGraphite("Unsupported table version: %u", this->version >> 16); + } + if (this->version >> 16 >= 3 && !table.ReadU32(&this->compHead)) { + return DropGraphite("Failed to read compHead"); + } + if (this->version >> 16 >= 5) { + switch ((this->compHead & SCHEME) >> 27) { + case 0: // uncompressed + break; + case 1: { // lz4 + if (prevent_decompression) { + return DropGraphite("Illegal nested compression"); + } + size_t decompressed_size = this->compHead & FULL_SIZE; + if (decompressed_size < length) { + return DropGraphite("Decompressed size is less than compressed size"); + } + if (decompressed_size == 0) { + return DropGraphite("Decompressed size is set to 0"); + } + // decompressed table must be <= 30MB + if (decompressed_size > 30 * 1024 * 1024) { + return DropGraphite("Decompressed size exceeds 30MB: %gMB", + decompressed_size / (1024.0 * 1024.0)); + } + std::vector decompressed(decompressed_size); + size_t outputSize = 0; + bool ret = mozilla::Compression::LZ4::decompressPartial( + reinterpret_cast(data + table.offset()), + table.remaining(), // input buffer size (input size + padding) + reinterpret_cast(decompressed.data()), + decompressed.size(), // target output size + &outputSize); // return output size + if (!ret || outputSize != decompressed.size()) { + return DropGraphite("Decompression failed"); + } + return this->Parse(decompressed.data(), decompressed.size(), true); + } + default: + return DropGraphite("Unknown compression scheme"); + } + } + if (!table.ReadU16(&this->numSub)) { + return DropGraphite("Failed to read numSub"); + } + if (this->version >> 16 >= 2 && !table.ReadU16(&this->reserved)) { + return DropGraphite("Failed to read reserved"); + } + if (this->version >> 16 >= 2 && this->reserved != 0) { + Warning("Nonzero reserved"); + } + + unsigned long last_offset = 0; + //this->offset.resize(this->numSub); + for (unsigned i = 0; i < this->numSub; ++i) { + this->offset.emplace_back(); + if (!table.ReadU32(&this->offset[i]) || this->offset[i] < last_offset) { + return DropGraphite("Failed to read offset[%u]", i); + } + last_offset = this->offset[i]; + } + + for (unsigned i = 0; i < this->numSub; ++i) { + if (table.offset() != this->offset[i]) { + return DropGraphite("Offset check failed for tables[%lu]", i); + } + SILSub subtable(this); + if (!subtable.ParsePart(table)) { + return DropGraphite("Failed to read tables[%u]", i); + } + tables.push_back(subtable); + } + + if (table.remaining()) { + return Warning("%zu bytes unparsed", table.remaining()); + } + return true; +} + +bool OpenTypeSILF::Serialize(OTSStream* out) { + if (!out->WriteU32(this->version) || + (this->version >> 16 >= 3 && !out->WriteU32(this->compHead)) || + !out->WriteU16(this->numSub) || + (this->version >> 16 >= 2 && !out->WriteU16(this->reserved)) || + !SerializeParts(this->offset, out) || + !SerializeParts(this->tables, out)) { + return Error("Failed to write table"); + } + return true; +} + +bool OpenTypeSILF::SILSub::ParsePart(Buffer& table) { + size_t init_offset = table.offset(); + if (parent->version >> 16 >= 3) { + if (!table.ReadU32(&this->ruleVersion)) { + return parent->Error("SILSub: Failed to read ruleVersion"); + } + if (!table.ReadU16(&this->passOffset)) { + return parent->Error("SILSub: Failed to read passOffset"); + } + if (!table.ReadU16(&this->pseudosOffset)) { + return parent->Error("SILSub: Failed to read pseudosOffset"); + } + } + if (!table.ReadU16(&this->maxGlyphID)) { + return parent->Error("SILSub: Failed to read maxGlyphID"); + } + if (!table.ReadS16(&this->extraAscent)) { + return parent->Error("SILSub: Failed to read extraAscent"); + } + if (!table.ReadS16(&this->extraDescent)) { + return parent->Error("SILSub: Failed to read extraDescent"); + } + if (!table.ReadU8(&this->numPasses)) { + return parent->Error("SILSub: Failed to read numPasses"); + } + if (!table.ReadU8(&this->iSubst) || this->iSubst > this->numPasses) { + return parent->Error("SILSub: Failed to read valid iSubst"); + } + if (!table.ReadU8(&this->iPos) || this->iPos > this->numPasses) { + return parent->Error("SILSub: Failed to read valid iPos"); + } + if (!table.ReadU8(&this->iJust) || this->iJust > this->numPasses) { + return parent->Error("SILSub: Failed to read valid iJust"); + } + if (!table.ReadU8(&this->iBidi) || + !(iBidi == 0xFF || this->iBidi <= this->iPos)) { + return parent->Error("SILSub: Failed to read valid iBidi"); + } + if (!table.ReadU8(&this->flags)) { + return parent->Error("SILSub: Failed to read flags"); + // checks omitted + } + if (!table.ReadU8(&this->maxPreContext)) { + return parent->Error("SILSub: Failed to read maxPreContext"); + } + if (!table.ReadU8(&this->maxPostContext)) { + return parent->Error("SILSub: Failed to read maxPostContext"); + } + if (!table.ReadU8(&this->attrPseudo)) { + return parent->Error("SILSub: Failed to read attrPseudo"); + } + if (!table.ReadU8(&this->attrBreakWeight)) { + return parent->Error("SILSub: Failed to read attrBreakWeight"); + } + if (!table.ReadU8(&this->attrDirectionality)) { + return parent->Error("SILSub: Failed to read attrDirectionality"); + } + if (parent->version >> 16 >= 2) { + if (!table.ReadU8(&this->attrMirroring)) { + return parent->Error("SILSub: Failed to read attrMirroring"); + } + if (!table.ReadU8(&this->attrSkipPasses)) { + return parent->Error("SILSub: Failed to read attrSkipPasses"); + } + + if (!table.ReadU8(&this->numJLevels)) { + return parent->Error("SILSub: Failed to read numJLevels"); + } + //this->jLevels.resize(this->numJLevels, parent); + for (unsigned i = 0; i < this->numJLevels; ++i) { + this->jLevels.emplace_back(parent); + if (!this->jLevels[i].ParsePart(table)) { + return parent->Error("SILSub: Failed to read jLevels[%u]", i); + } + } + } + + if (!table.ReadU16(&this->numLigComp)) { + return parent->Error("SILSub: Failed to read numLigComp"); + } + if (!table.ReadU8(&this->numUserDefn)) { + return parent->Error("SILSub: Failed to read numUserDefn"); + } + if (!table.ReadU8(&this->maxCompPerLig)) { + return parent->Error("SILSub: Failed to read maxCompPerLig"); + } + if (!table.ReadU8(&this->direction)) { + return parent->Error("SILSub: Failed to read direction"); + } + if (!table.ReadU8(&this->attCollisions)) { + return parent->Error("SILSub: Failed to read attCollisions"); + } + if (parent->version >> 16 < 5 && this->attCollisions != 0) { + parent->Warning("SILSub: Nonzero attCollisions (reserved before v5)"); + } + if (!table.ReadU8(&this->reserved4)) { + return parent->Error("SILSub: Failed to read reserved4"); + } + if (this->reserved4 != 0) { + parent->Warning("SILSub: Nonzero reserved4"); + } + if (!table.ReadU8(&this->reserved5)) { + return parent->Error("SILSub: Failed to read reserved5"); + } + if (this->reserved5 != 0) { + parent->Warning("SILSub: Nonzero reserved5"); + } + if (parent->version >> 16 >= 2) { + if (!table.ReadU8(&this->reserved6)) { + return parent->Error("SILSub: Failed to read reserved6"); + } + if (this->reserved6 != 0) { + parent->Warning("SILSub: Nonzero reserved6"); + } + + if (!table.ReadU8(&this->numCritFeatures)) { + return parent->Error("SILSub: Failed to read numCritFeatures"); + } + //this->critFeatures.resize(this->numCritFeatures); + for (unsigned i = 0; i < this->numCritFeatures; ++i) { + this->critFeatures.emplace_back(); + if (!table.ReadU16(&this->critFeatures[i])) { + return parent->Error("SILSub: Failed to read critFeatures[%u]", i); + } + } + + if (!table.ReadU8(&this->reserved7)) { + return parent->Error("SILSub: Failed to read reserved7"); + } + if (this->reserved7 != 0) { + parent->Warning("SILSub: Nonzero reserved7"); + } + } + + if (!table.ReadU8(&this->numScriptTag)) { + return parent->Error("SILSub: Failed to read numScriptTag"); + } + //this->scriptTag.resize(this->numScriptTag); + for (unsigned i = 0; i < this->numScriptTag; ++i) { + this->scriptTag.emplace_back(); + if (!table.ReadU32(&this->scriptTag[i])) { + return parent->Error("SILSub: Failed to read scriptTag[%u]", i); + } + } + + if (!table.ReadU16(&this->lbGID)) { + return parent->Error("SILSub: Failed to read lbGID"); + } + if (this->lbGID > this->maxGlyphID) { + parent->Warning("SILSub: lbGID %u outside range 0..%u, replaced with 0", + this->lbGID, this->maxGlyphID); + this->lbGID = 0; + } + + if (parent->version >> 16 >= 3 && + table.offset() != init_offset + this->passOffset) { + return parent->Error("SILSub: passOffset check failed"); + } + unsigned long last_oPass = 0; + //this->oPasses.resize(static_cast(this->numPasses) + 1); + for (unsigned i = 0; i <= this->numPasses; ++i) { + this->oPasses.emplace_back(); + if (!table.ReadU32(&this->oPasses[i]) || this->oPasses[i] < last_oPass) { + return false; + } + last_oPass = this->oPasses[i]; + } + + if (parent->version >> 16 >= 3 && + table.offset() != init_offset + this->pseudosOffset) { + return parent->Error("SILSub: pseudosOffset check failed"); + } + if (!table.ReadU16(&this->numPseudo)) { + return parent->Error("SILSub: Failed to read numPseudo"); + } + + // The following three fields are deprecated and ignored. We fix them up here + // just for internal consistency, but the Graphite engine doesn't care. + if (!table.ReadU16(&this->searchPseudo) || + !table.ReadU16(&this->pseudoSelector) || + !table.ReadU16(&this->pseudoShift)) { + return parent->Error("SILSub: Failed to read searchPseudo..pseudoShift"); + } + if (this->numPseudo == 0) { + if (this->searchPseudo != 0 || this->pseudoSelector != 0 || this->pseudoShift != 0) { + this->searchPseudo = this->pseudoSelector = this->pseudoShift = 0; + } + } else { + unsigned floorLog2 = std::floor(std::log2(this->numPseudo)); + if (this->searchPseudo != 6 * (unsigned)std::pow(2, floorLog2) || + this->pseudoSelector != floorLog2 || + this->pseudoShift != 6 * this->numPseudo - this->searchPseudo) { + this->searchPseudo = 6 * (unsigned)std::pow(2, floorLog2); + this->pseudoSelector = floorLog2; + this->pseudoShift = 6 * this->numPseudo - this->searchPseudo; + } + } + + //this->pMaps.resize(this->numPseudo, parent); + for (unsigned i = 0; i < numPseudo; i++) { + this->pMaps.emplace_back(parent); + if (!this->pMaps[i].ParsePart(table)) { + return parent->Error("SILSub: Failed to read pMaps[%u]", i); + } + } + + if (!this->classes.ParsePart(table)) { + return parent->Error("SILSub: Failed to read classes"); + } + + //this->passes.resize(this->numPasses, parent); + for (unsigned i = 0; i < this->numPasses; ++i) { + this->passes.emplace_back(parent); + if (table.offset() != init_offset + this->oPasses[i]) { + return parent->Error("SILSub: Offset check failed for passes[%u]", i); + } + if (!this->passes[i].ParsePart(table, init_offset, this->oPasses[i+1])) { + return parent->Error("SILSub: Failed to read passes[%u]", i); + } + } + return true; +} + +bool OpenTypeSILF::SILSub::SerializePart(OTSStream* out) const { + if ((parent->version >> 16 >= 3 && + (!out->WriteU32(this->ruleVersion) || + !out->WriteU16(this->passOffset) || + !out->WriteU16(this->pseudosOffset))) || + !out->WriteU16(this->maxGlyphID) || + !out->WriteS16(this->extraAscent) || + !out->WriteS16(this->extraDescent) || + !out->WriteU8(this->numPasses) || + !out->WriteU8(this->iSubst) || + !out->WriteU8(this->iPos) || + !out->WriteU8(this->iJust) || + !out->WriteU8(this->iBidi) || + !out->WriteU8(this->flags) || + !out->WriteU8(this->maxPreContext) || + !out->WriteU8(this->maxPostContext) || + !out->WriteU8(this->attrPseudo) || + !out->WriteU8(this->attrBreakWeight) || + !out->WriteU8(this->attrDirectionality) || + (parent->version >> 16 >= 2 && + (!out->WriteU8(this->attrMirroring) || + !out->WriteU8(this->attrSkipPasses) || + !out->WriteU8(this->numJLevels) || + !SerializeParts(this->jLevels, out))) || + !out->WriteU16(this->numLigComp) || + !out->WriteU8(this->numUserDefn) || + !out->WriteU8(this->maxCompPerLig) || + !out->WriteU8(this->direction) || + !out->WriteU8(this->attCollisions) || + !out->WriteU8(this->reserved4) || + !out->WriteU8(this->reserved5) || + (parent->version >> 16 >= 2 && + (!out->WriteU8(this->reserved6) || + !out->WriteU8(this->numCritFeatures) || + !SerializeParts(this->critFeatures, out) || + !out->WriteU8(this->reserved7))) || + !out->WriteU8(this->numScriptTag) || + !SerializeParts(this->scriptTag, out) || + !out->WriteU16(this->lbGID) || + !SerializeParts(this->oPasses, out) || + !out->WriteU16(this->numPseudo) || + !out->WriteU16(this->searchPseudo) || + !out->WriteU16(this->pseudoSelector) || + !out->WriteU16(this->pseudoShift) || + !SerializeParts(this->pMaps, out) || + !this->classes.SerializePart(out) || + !SerializeParts(this->passes, out)) { + return parent->Error("SILSub: Failed to write"); + } + return true; +} + +bool OpenTypeSILF::SILSub:: +JustificationLevel::ParsePart(Buffer& table) { + if (!table.ReadU8(&this->attrStretch)) { + return parent->Error("JustificationLevel: Failed to read attrStretch"); + } + if (!table.ReadU8(&this->attrShrink)) { + return parent->Error("JustificationLevel: Failed to read attrShrink"); + } + if (!table.ReadU8(&this->attrStep)) { + return parent->Error("JustificationLevel: Failed to read attrStep"); + } + if (!table.ReadU8(&this->attrWeight)) { + return parent->Error("JustificationLevel: Failed to read attrWeight"); + } + if (!table.ReadU8(&this->runto)) { + return parent->Error("JustificationLevel: Failed to read runto"); + } + if (!table.ReadU8(&this->reserved)) { + return parent->Error("JustificationLevel: Failed to read reserved"); + } + if (this->reserved != 0) { + parent->Warning("JustificationLevel: Nonzero reserved"); + } + if (!table.ReadU8(&this->reserved2)) { + return parent->Error("JustificationLevel: Failed to read reserved2"); + } + if (this->reserved2 != 0) { + parent->Warning("JustificationLevel: Nonzero reserved2"); + } + if (!table.ReadU8(&this->reserved3)) { + return parent->Error("JustificationLevel: Failed to read reserved3"); + } + if (this->reserved3 != 0) { + parent->Warning("JustificationLevel: Nonzero reserved3"); + } + return true; +} + +bool OpenTypeSILF::SILSub:: +JustificationLevel::SerializePart(OTSStream* out) const { + if (!out->WriteU8(this->attrStretch) || + !out->WriteU8(this->attrShrink) || + !out->WriteU8(this->attrStep) || + !out->WriteU8(this->attrWeight) || + !out->WriteU8(this->runto) || + !out->WriteU8(this->reserved) || + !out->WriteU8(this->reserved2) || + !out->WriteU8(this->reserved3)) { + return parent->Error("JustificationLevel: Failed to write"); + } + return true; +} + +bool OpenTypeSILF::SILSub:: +PseudoMap::ParsePart(Buffer& table) { + if (parent->version >> 16 >= 2 && !table.ReadU32(&this->unicode)) { + return parent->Error("PseudoMap: Failed to read unicode"); + } + if (parent->version >> 16 == 1) { + uint16_t unicode; + if (!table.ReadU16(&unicode)) { + return parent->Error("PseudoMap: Failed to read unicode"); + } + this->unicode = unicode; + } + if (!table.ReadU16(&this->nPseudo)) { + return parent->Error("PseudoMap: Failed to read nPseudo"); + } + return true; +} + +bool OpenTypeSILF::SILSub:: +PseudoMap::SerializePart(OTSStream* out) const { + if ((parent->version >> 16 >= 2 && !out->WriteU32(this->unicode)) || + (parent->version >> 16 == 1 && + !out->WriteU16(static_cast(this->unicode))) || + !out->WriteU16(this->nPseudo)) { + return parent->Error("PseudoMap: Failed to write"); + } + return true; +} + +bool OpenTypeSILF::SILSub:: +ClassMap::ParsePart(Buffer& table) { + size_t init_offset = table.offset(); + if (!table.ReadU16(&this->numClass)) { + return parent->Error("ClassMap: Failed to read numClass"); + } + if (!table.ReadU16(&this->numLinear) || this->numLinear > this->numClass) { + return parent->Error("ClassMap: Failed to read valid numLinear"); + } + + //this->oClass.resize(static_cast(this->numClass) + 1); + if (parent->version >> 16 >= 4) { + unsigned long last_oClass = 0; + for (unsigned long i = 0; i <= this->numClass; ++i) { + this->oClass.emplace_back(); + if (!table.ReadU32(&this->oClass[i]) || this->oClass[i] < last_oClass) { + return parent->Error("ClassMap: Failed to read oClass[%lu]", i); + } + last_oClass = this->oClass[i]; + } + } + if (parent->version >> 16 < 4) { + unsigned last_oClass = 0; + for (unsigned long i = 0; i <= this->numClass; ++i) { + uint16_t offset; + if (!table.ReadU16(&offset) || offset < last_oClass) { + return parent->Error("ClassMap: Failed to read oClass[%lu]", i); + } + last_oClass = offset; + this->oClass.push_back(static_cast(offset)); + } + } + + if (table.offset() - init_offset > this->oClass[this->numLinear]) { + return parent->Error("ClassMap: Failed to calculate length of glyphs"); + } + unsigned long glyphs_len = (this->oClass[this->numLinear] - + (table.offset() - init_offset))/2; + //this->glyphs.resize(glyphs_len); + for (unsigned long i = 0; i < glyphs_len; ++i) { + this->glyphs.emplace_back(); + if (!table.ReadU16(&this->glyphs[i])) { + return parent->Error("ClassMap: Failed to read glyphs[%lu]", i); + } + } + + unsigned lookups_len = this->numClass - this->numLinear; + // this->numLinear <= this->numClass + //this->lookups.resize(lookups_len, parent); + for (unsigned i = 0; i < lookups_len; ++i) { + this->lookups.emplace_back(parent); + if (table.offset() != init_offset + oClass[this->numLinear + i]) { + return parent->Error("ClassMap: Offset check failed for lookups[%u]", i); + } + if (!this->lookups[i].ParsePart(table)) { + return parent->Error("ClassMap: Failed to read lookups[%u]", i); + } + } + return true; +} + +bool OpenTypeSILF::SILSub:: +ClassMap::SerializePart(OTSStream* out) const { + if (!out->WriteU16(this->numClass) || + !out->WriteU16(this->numLinear) || + (parent->version >> 16 >= 4 && !SerializeParts(this->oClass, out)) || + (parent->version >> 16 < 4 && + ![&] { + for (uint32_t offset : this->oClass) { + if (!out->WriteU16(static_cast(offset))) { + return false; + } + } + return true; + }()) || + !SerializeParts(this->glyphs, out) || + !SerializeParts(this->lookups, out)) { + return parent->Error("ClassMap: Failed to write"); + } + return true; +} + +bool OpenTypeSILF::SILSub::ClassMap:: +LookupClass::ParsePart(Buffer& table) { + if (!table.ReadU16(&this->numIDs)) { + return parent->Error("LookupClass: Failed to read numIDs"); + } + if (!table.ReadU16(&this->searchRange) || + !table.ReadU16(&this->entrySelector) || + !table.ReadU16(&this->rangeShift)) { + return parent->Error("LookupClass: Failed to read searchRange..rangeShift"); + } + if (this->numIDs == 0) { + if (this->searchRange != 0 || this->entrySelector != 0 || this->rangeShift != 0) { + parent->Warning("LookupClass: Correcting binary-search header for zero-length LookupPair list"); + this->searchRange = this->entrySelector = this->rangeShift = 0; + } + } else { + unsigned floorLog2 = std::floor(std::log2(this->numIDs)); + if (this->searchRange != (unsigned)std::pow(2, floorLog2) || + this->entrySelector != floorLog2 || + this->rangeShift != this->numIDs - this->searchRange) { + parent->Warning("LookupClass: Correcting binary-search header for LookupPair list"); + this->searchRange = (unsigned)std::pow(2, floorLog2); + this->entrySelector = floorLog2; + this->rangeShift = this->numIDs - this->searchRange; + } + } + + //this->lookups.resize(this->numIDs, parent); + for (unsigned i = 0; i < numIDs; ++i) { + this->lookups.emplace_back(parent); + if (!this->lookups[i].ParsePart(table)) { + return parent->Error("LookupClass: Failed to read lookups[%u]", i); + } + } + return true; +} + +bool OpenTypeSILF::SILSub::ClassMap:: +LookupClass::SerializePart(OTSStream* out) const { + if (!out->WriteU16(this->numIDs) || + !out->WriteU16(this->searchRange) || + !out->WriteU16(this->entrySelector) || + !out->WriteU16(this->rangeShift) || + !SerializeParts(this->lookups, out)) { + return parent->Error("LookupClass: Failed to write"); + } + return true; +} + +bool OpenTypeSILF::SILSub::ClassMap::LookupClass:: +LookupPair::ParsePart(Buffer& table) { + if (!table.ReadU16(&this->glyphId)) { + return parent->Error("LookupPair: Failed to read glyphId"); + } + if (!table.ReadU16(&this->index)) { + return parent->Error("LookupPair: Failed to read index"); + } + return true; +} + +bool OpenTypeSILF::SILSub::ClassMap::LookupClass:: +LookupPair::SerializePart(OTSStream* out) const { + if (!out->WriteU16(this->glyphId) || + !out->WriteU16(this->index)) { + return parent->Error("LookupPair: Failed to write"); + } + return true; +} + +bool OpenTypeSILF::SILSub:: +SILPass::ParsePart(Buffer& table, const size_t SILSub_init_offset, + const size_t next_pass_offset) { + size_t init_offset = table.offset(); + if (!table.ReadU8(&this->flags)) { + return parent->Error("SILPass: Failed to read flags"); + // checks omitted + } + if (!table.ReadU8(&this->maxRuleLoop)) { + return parent->Error("SILPass: Failed to read valid maxRuleLoop"); + } + if (!table.ReadU8(&this->maxRuleContext)) { + return parent->Error("SILPass: Failed to read maxRuleContext"); + } + if (!table.ReadU8(&this->maxBackup)) { + return parent->Error("SILPass: Failed to read maxBackup"); + } + if (!table.ReadU16(&this->numRules)) { + return parent->Error("SILPass: Failed to read numRules"); + } + if (parent->version >> 16 >= 2) { + if (!table.ReadU16(&this->fsmOffset)) { + return parent->Error("SILPass: Failed to read fsmOffset"); + } + if (!table.ReadU32(&this->pcCode) || + (parent->version >= 3 && this->pcCode < this->fsmOffset)) { + return parent->Error("SILPass: Failed to read pcCode"); + } + } + if (!table.ReadU32(&this->rcCode) || + (parent->version >> 16 >= 2 && this->rcCode < this->pcCode)) { + return parent->Error("SILPass: Failed to read valid rcCode"); + } + if (!table.ReadU32(&this->aCode) || this->aCode < this->rcCode) { + return parent->Error("SILPass: Failed to read valid aCode"); + } + if (!table.ReadU32(&this->oDebug) || + (this->oDebug && this->oDebug < this->aCode)) { + return parent->Error("SILPass: Failed to read valid oDebug"); + } + if (parent->version >> 16 >= 3 && + table.offset() != init_offset + this->fsmOffset) { + return parent->Error("SILPass: fsmOffset check failed"); + } + if (!table.ReadU16(&this->numRows) || + (this->oDebug && this->numRows < this->numRules)) { + return parent->Error("SILPass: Failed to read valid numRows"); + } + if (!table.ReadU16(&this->numTransitional)) { + return parent->Error("SILPass: Failed to read numTransitional"); + } + if (!table.ReadU16(&this->numSuccess)) { + return parent->Error("SILPass: Failed to read numSuccess"); + } + if (!table.ReadU16(&this->numColumns)) { + return parent->Error("SILPass: Failed to read numColumns"); + } + if (!table.ReadU16(&this->numRange)) { + return parent->Error("SILPass: Failed to read numRange"); + } + + // The following three fields are deprecated and ignored. We fix them up here + // just for internal consistency, but the Graphite engine doesn't care. + if (!table.ReadU16(&this->searchRange) || + !table.ReadU16(&this->entrySelector) || + !table.ReadU16(&this->rangeShift)) { + return parent->Error("SILPass: Failed to read searchRange..rangeShift"); + } + if (this->numRange == 0) { + if (this->searchRange != 0 || this->entrySelector != 0 || this->rangeShift != 0) { + this->searchRange = this->entrySelector = this->rangeShift = 0; + } + } else { + unsigned floorLog2 = std::floor(std::log2(this->numRange)); + if (this->searchRange != 6 * (unsigned)std::pow(2, floorLog2) || + this->entrySelector != floorLog2 || + this->rangeShift != 6 * this->numRange - this->searchRange) { + this->searchRange = 6 * (unsigned)std::pow(2, floorLog2); + this->entrySelector = floorLog2; + this->rangeShift = 6 * this->numRange - this->searchRange; + } + } + + //this->ranges.resize(this->numRange, parent); + for (unsigned i = 0 ; i < this->numRange; ++i) { + this->ranges.emplace_back(parent); + if (!this->ranges[i].ParsePart(table)) { + return parent->Error("SILPass: Failed to read ranges[%u]", i); + } + } + unsigned ruleMap_len = 0; // maximum value in oRuleMap + //this->oRuleMap.resize(static_cast(this->numSuccess) + 1); + for (unsigned long i = 0; i <= this->numSuccess; ++i) { + this->oRuleMap.emplace_back(); + if (!table.ReadU16(&this->oRuleMap[i])) { + return parent->Error("SILPass: Failed to read oRuleMap[%u]", i); + } + if (oRuleMap[i] > ruleMap_len) { + ruleMap_len = oRuleMap[i]; + } + } + + //this->ruleMap.resize(ruleMap_len); + for (unsigned i = 0; i < ruleMap_len; ++i) { + this->ruleMap.emplace_back(); + if (!table.ReadU16(&this->ruleMap[i])) { + return parent->Error("SILPass: Failed to read ruleMap[%u]", i); + } + } + + if (!table.ReadU8(&this->minRulePreContext)) { + return parent->Error("SILPass: Failed to read minRulePreContext"); + } + if (!table.ReadU8(&this->maxRulePreContext) || + this->maxRulePreContext < this->minRulePreContext) { + return parent->Error("SILPass: Failed to read valid maxRulePreContext"); + } + + unsigned startStates_len = this->maxRulePreContext - this->minRulePreContext + + 1; + // this->minRulePreContext <= this->maxRulePreContext + //this->startStates.resize(startStates_len); + for (unsigned i = 0; i < startStates_len; ++i) { + this->startStates.emplace_back(); + if (!table.ReadS16(&this->startStates[i])) { + return parent->Error("SILPass: Failed to read startStates[%u]", i); + } + } + + //this->ruleSortKeys.resize(this->numRules); + for (unsigned i = 0; i < this->numRules; ++i) { + this->ruleSortKeys.emplace_back(); + if (!table.ReadU16(&this->ruleSortKeys[i])) { + return parent->Error("SILPass: Failed to read ruleSortKeys[%u]", i); + } + } + + //this->rulePreContext.resize(this->numRules); + for (unsigned i = 0; i < this->numRules; ++i) { + this->rulePreContext.emplace_back(); + if (!table.ReadU8(&this->rulePreContext[i])) { + return parent->Error("SILPass: Failed to read rulePreContext[%u]", i); + } + } + + if (parent->version >> 16 >= 2) { + if (!table.ReadU8(&this->collisionThreshold)) { + return parent->Error("SILPass: Failed to read collisionThreshold"); + } + if (!table.ReadU16(&this->pConstraint)) { + return parent->Error("SILPass: Failed to read pConstraint"); + } + } + + unsigned long ruleConstraints_len = this->aCode - this->rcCode; + // this->rcCode <= this->aCode + //this->oConstraints.resize(static_cast(this->numRules) + 1); + for (unsigned long i = 0; i <= this->numRules; ++i) { + this->oConstraints.emplace_back(); + if (!table.ReadU16(&this->oConstraints[i]) || + this->oConstraints[i] > ruleConstraints_len) { + return parent->Error("SILPass: Failed to read valid oConstraints[%lu]", + i); + } + } + + if (!this->oDebug && this->aCode > next_pass_offset) { + return parent->Error("SILPass: Failed to calculate length of actions"); + } + unsigned long actions_len = this->oDebug ? this->oDebug - this->aCode : + next_pass_offset - this->aCode; + // if this->oDebug, then this->aCode <= this->oDebug + //this->oActions.resize(static_cast(this->numRules) + 1); + for (unsigned long i = 0; i <= this->numRules; ++i) { + this->oActions.emplace_back(); + if (!table.ReadU16(&this->oActions[i]) || + (this->oActions[i] > actions_len)) { + return parent->Error("SILPass: Failed to read valid oActions[%lu]", i); + } + } + + //this->stateTrans.resize(this->numTransitional); + for (unsigned i = 0; i < this->numTransitional; ++i) { + this->stateTrans.emplace_back(); + //this->stateTrans[i].resize(this->numColumns); + for (unsigned j = 0; j < this->numColumns; ++j) { + this->stateTrans[i].emplace_back(); + if (!table.ReadU16(&stateTrans[i][j])) { + return parent->Error("SILPass: Failed to read stateTrans[%u][%u]", + i, j); + } + } + } + + if (parent->version >> 16 >= 2) { + if (!table.ReadU8(&this->reserved2)) { + return parent->Error("SILPass: Failed to read reserved2"); + } + if (this->reserved2 != 0) { + parent->Warning("SILPass: Nonzero reserved2"); + } + + if (table.offset() != SILSub_init_offset + this->pcCode) { + return parent->Error("SILPass: pcCode check failed"); + } + //this->passConstraints.resize(this->pConstraint); + for (unsigned i = 0; i < this->pConstraint; ++i) { + this->passConstraints.emplace_back(); + if (!table.ReadU8(&this->passConstraints[i])) { + return parent->Error("SILPass: Failed to read passConstraints[%u]", i); + } + } + } + + if (table.offset() != SILSub_init_offset + this->rcCode) { + return parent->Error("SILPass: rcCode check failed"); + } + //this->ruleConstraints.resize(ruleConstraints_len); // calculated above + for (unsigned long i = 0; i < ruleConstraints_len; ++i) { + this->ruleConstraints.emplace_back(); + if (!table.ReadU8(&this->ruleConstraints[i])) { + return parent->Error("SILPass: Failed to read ruleConstraints[%u]", i); + } + } + + if (table.offset() != SILSub_init_offset + this->aCode) { + return parent->Error("SILPass: aCode check failed"); + } + //this->actions.resize(actions_len); // calculated above + for (unsigned long i = 0; i < actions_len; ++i) { + this->actions.emplace_back(); + if (!table.ReadU8(&this->actions[i])) { + return parent->Error("SILPass: Failed to read actions[%u]", i); + } + } + + if (this->oDebug) { + OpenTypeNAME* name = static_cast( + parent->GetFont()->GetTypedTable(OTS_TAG_NAME)); + if (!name) { + return parent->Error("SILPass: Required name table is missing"); + } + + if (table.offset() != SILSub_init_offset + this->oDebug) { + return parent->Error("SILPass: oDebug check failed"); + } + //this->dActions.resize(this->numRules); + for (unsigned i = 0; i < this->numRules; ++i) { + this->dActions.emplace_back(); + if (!table.ReadU16(&this->dActions[i]) || + !name->IsValidNameId(this->dActions[i])) { + return parent->Error("SILPass: Failed to read valid dActions[%u]", i); + } + } + + unsigned dStates_len = this->numRows - this->numRules; + // this->numRules <= this->numRows + //this->dStates.resize(dStates_len); + for (unsigned i = 0; i < dStates_len; ++i) { + this->dStates.emplace_back(); + if (!table.ReadU16(&this->dStates[i]) || + !name->IsValidNameId(this->dStates[i])) { + return parent->Error("SILPass: Failed to read valid dStates[%u]", i); + } + } + + //this->dCols.resize(this->numRules); + for (unsigned i = 0; i < this->numRules; ++i) { + this->dCols.emplace_back(); + if (!table.ReadU16(&this->dCols[i]) || + !name->IsValidNameId(this->dCols[i])) { + return parent->Error("SILPass: Failed to read valid dCols[%u]"); + } + } + } + return true; +} + +bool OpenTypeSILF::SILSub:: +SILPass::SerializePart(OTSStream* out) const { + if (!out->WriteU8(this->flags) || + !out->WriteU8(this->maxRuleLoop) || + !out->WriteU8(this->maxRuleContext) || + !out->WriteU8(this->maxBackup) || + !out->WriteU16(this->numRules) || + (parent->version >> 16 >= 2 && + (!out->WriteU16(this->fsmOffset) || + !out->WriteU32(this->pcCode))) || + !out->WriteU32(this->rcCode) || + !out->WriteU32(this->aCode) || + !out->WriteU32(this->oDebug) || + !out->WriteU16(this->numRows) || + !out->WriteU16(this->numTransitional) || + !out->WriteU16(this->numSuccess) || + !out->WriteU16(this->numColumns) || + !out->WriteU16(this->numRange) || + !out->WriteU16(this->searchRange) || + !out->WriteU16(this->entrySelector) || + !out->WriteU16(this->rangeShift) || + !SerializeParts(this->ranges, out) || + !SerializeParts(this->oRuleMap, out) || + !SerializeParts(this->ruleMap, out) || + !out->WriteU8(this->minRulePreContext) || + !out->WriteU8(this->maxRulePreContext) || + !SerializeParts(this->startStates, out) || + !SerializeParts(this->ruleSortKeys, out) || + !SerializeParts(this->rulePreContext, out) || + (parent->version >> 16 >= 2 && + (!out->WriteU8(this->collisionThreshold) || + !out->WriteU16(this->pConstraint))) || + !SerializeParts(this->oConstraints, out) || + !SerializeParts(this->oActions, out) || + !SerializeParts(this->stateTrans, out) || + (parent->version >> 16 >= 2 && + (!out->WriteU8(this->reserved2) || + !SerializeParts(this->passConstraints, out))) || + !SerializeParts(this->ruleConstraints, out) || + !SerializeParts(this->actions, out) || + !SerializeParts(this->dActions, out) || + !SerializeParts(this->dStates, out) || + !SerializeParts(this->dCols, out)) { + return parent->Error("SILPass: Failed to write"); + } + return true; +} + +bool OpenTypeSILF::SILSub::SILPass:: +PassRange::ParsePart(Buffer& table) { + if (!table.ReadU16(&this->firstId)) { + return parent->Error("PassRange: Failed to read firstId"); + } + if (!table.ReadU16(&this->lastId)) { + return parent->Error("PassRange: Failed to read lastId"); + } + if (!table.ReadU16(&this->colId)) { + return parent->Error("PassRange: Failed to read colId"); + } + return true; +} + +bool OpenTypeSILF::SILSub::SILPass:: +PassRange::SerializePart(OTSStream* out) const { + if (!out->WriteU16(this->firstId) || + !out->WriteU16(this->lastId) || + !out->WriteU16(this->colId)) { + return parent->Error("PassRange: Failed to write"); + } + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/silf.h b/gfx/ots/src/silf.h new file mode 100644 index 000000000..b6c08032f --- /dev/null +++ b/gfx/ots/src/silf.h @@ -0,0 +1,196 @@ +// Copyright (c) 2009-2017 The OTS 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_SILF_H_ +#define OTS_SILF_H_ + +#include + +#include "ots.h" +#include "graphite.h" + +namespace ots { + +class OpenTypeSILF : public Table { + public: + explicit OpenTypeSILF(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length) { + return this->Parse(data, length, false); + } + bool Serialize(OTSStream* out); + + private: + bool Parse(const uint8_t* data, size_t length, bool prevent_decompression); + struct SILSub : public TablePart { + explicit SILSub(OpenTypeSILF* parent) + : TablePart(parent), classes(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + struct JustificationLevel : public TablePart { + explicit JustificationLevel(OpenTypeSILF* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + uint8_t attrStretch; + uint8_t attrShrink; + uint8_t attrStep; + uint8_t attrWeight; + uint8_t runto; + uint8_t reserved; + uint8_t reserved2; + uint8_t reserved3; + }; + struct PseudoMap : public TablePart { + explicit PseudoMap(OpenTypeSILF* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + uint32_t unicode; + uint16_t nPseudo; + }; + struct ClassMap : public TablePart { + explicit ClassMap(OpenTypeSILF* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + struct LookupClass : public TablePart { + explicit LookupClass(OpenTypeSILF* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + struct LookupPair : public TablePart { + explicit LookupPair(OpenTypeSILF* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + uint16_t glyphId; + uint16_t index; + }; + uint16_t numIDs; + uint16_t searchRange; + uint16_t entrySelector; + uint16_t rangeShift; + std::vector lookups; + }; + uint16_t numClass; + uint16_t numLinear; + std::vector oClass; // uint16_t before v4 + std::vector glyphs; + std::vector lookups; + }; + struct SILPass : public TablePart { + explicit SILPass(OpenTypeSILF* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table) { return false; } + bool ParsePart(Buffer& table, const size_t SILSub_init_offset, + const size_t next_pass_offset); + bool SerializePart(OTSStream* out) const; + struct PassRange : public TablePart { + explicit PassRange(OpenTypeSILF* parent) + : TablePart(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + uint16_t firstId; + uint16_t lastId; + uint16_t colId; + }; + uint8_t flags; + uint8_t maxRuleLoop; + uint8_t maxRuleContext; + uint8_t maxBackup; + uint16_t numRules; + uint16_t fsmOffset; + uint32_t pcCode; + uint32_t rcCode; + uint32_t aCode; + uint32_t oDebug; + uint16_t numRows; + uint16_t numTransitional; + uint16_t numSuccess; + uint16_t numColumns; + uint16_t numRange; + uint16_t searchRange; + uint16_t entrySelector; + uint16_t rangeShift; + std::vector ranges; + std::vector oRuleMap; + std::vector ruleMap; + uint8_t minRulePreContext; + uint8_t maxRulePreContext; + std::vector startStates; + std::vector ruleSortKeys; + std::vector rulePreContext; + uint8_t collisionThreshold; // reserved before v5 + uint16_t pConstraint; + std::vector oConstraints; + std::vector oActions; + std::vector> stateTrans; + uint8_t reserved2; + std::vector passConstraints; + std::vector ruleConstraints; + std::vector actions; + std::vector dActions; + std::vector dStates; + std::vector dCols; + }; + uint32_t ruleVersion; + uint16_t passOffset; + uint16_t pseudosOffset; + uint16_t maxGlyphID; + int16_t extraAscent; + int16_t extraDescent; + uint8_t numPasses; + uint8_t iSubst; + uint8_t iPos; + uint8_t iJust; + uint8_t iBidi; + uint8_t flags; + uint8_t maxPreContext; + uint8_t maxPostContext; + uint8_t attrPseudo; + uint8_t attrBreakWeight; + uint8_t attrDirectionality; + uint8_t attrMirroring; // reserved before v4 + uint8_t attrSkipPasses; // reserved2 before v4 + uint8_t numJLevels; + std::vector jLevels; + uint16_t numLigComp; + uint8_t numUserDefn; + uint8_t maxCompPerLig; + uint8_t direction; + uint8_t attCollisions; // reserved3 before v5 + uint8_t reserved4; + uint8_t reserved5; + uint8_t reserved6; + uint8_t numCritFeatures; + std::vector critFeatures; + uint8_t reserved7; + uint8_t numScriptTag; + std::vector scriptTag; + uint16_t lbGID; + std::vector oPasses; + uint16_t numPseudo; + uint16_t searchPseudo; + uint16_t pseudoSelector; + uint16_t pseudoShift; + std::vector pMaps; + ClassMap classes; + std::vector passes; + }; + uint32_t version; + uint32_t compHead; // compression header + static const uint32_t SCHEME = 0xF8000000; + static const uint32_t FULL_SIZE = 0x07FFFFFF; + static const uint32_t COMPILER_VERSION = 0x07FFFFFF; + uint16_t numSub; + uint16_t reserved; + std::vector offset; + std::vector tables; +}; + +} // namespace ots + +#endif // OTS_SILF_H_ diff --git a/gfx/ots/src/sill.cc b/gfx/ots/src/sill.cc new file mode 100644 index 000000000..c7b20a980 --- /dev/null +++ b/gfx/ots/src/sill.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2009-2017 The OTS 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 "sill.h" + +#include "feat.h" +#include +#include + +namespace ots { + +bool OpenTypeSILL::Parse(const uint8_t* data, size_t length) { + if (GetFont()->dropped_graphite) { + return Drop("Skipping Graphite table"); + } + Buffer table(data, length); + + if (!table.ReadU32(&this->version) || this->version >> 16 != 1) { + return Drop("Failed to read valid version"); + } + if (!table.ReadU16(&this->numLangs)) { + return Drop("Failed to read numLangs"); + } + + // The following three fields are deprecated and ignored. We fix them up here + // just for internal consistency, but the Graphite engine doesn't care. + if (!table.ReadU16(&this->searchRange) || + !table.ReadU16(&this->entrySelector) || + !table.ReadU16(&this->rangeShift)) { + return Drop("Failed to read searchRange..rangeShift"); + } + if (this->numLangs == 0) { + if (this->searchRange != 0 || this->entrySelector != 0 || this->rangeShift != 0) { + this->searchRange = this->entrySelector = this->rangeShift = 0; + } + } else { + unsigned floorLog2 = std::floor(std::log2(this->numLangs)); + if (this->searchRange != (unsigned)std::pow(2, floorLog2) || + this->entrySelector != floorLog2 || + this->rangeShift != this->numLangs - this->searchRange) { + this->searchRange = (unsigned)std::pow(2, floorLog2); + this->entrySelector = floorLog2; + this->rangeShift = this->numLangs - this->searchRange; + } + } + + std::unordered_set unverified; + //this->entries.resize(static_cast(this->numLangs) + 1, this); + for (unsigned long i = 0; i <= this->numLangs; ++i) { + this->entries.emplace_back(this); + LanguageEntry& entry = this->entries[i]; + if (!entry.ParsePart(table)) { + return Drop("Failed to read entries[%u]", i); + } + for (unsigned j = 0; j < entry.numSettings; ++j) { + size_t offset = entry.offset + j * 8; + if (offset < entry.offset || offset > length) { + return DropGraphite("Invalid LangFeatureSetting offset %zu/%zu", + offset, length); + } + unverified.insert(offset); + // need to verify that this LanguageEntry points to valid + // LangFeatureSetting + } + } + + while (table.remaining()) { + unverified.erase(table.offset()); + LangFeatureSetting setting(this); + if (!setting.ParsePart(table)) { + return Drop("Failed to read a LangFeatureSetting"); + } + settings.push_back(setting); + } + + if (!unverified.empty()) { + return Drop("%zu incorrect offsets into settings", unverified.size()); + } + if (table.remaining()) { + return Warning("%zu bytes unparsed", table.remaining()); + } + return true; +} + +bool OpenTypeSILL::Serialize(OTSStream* out) { + if (!out->WriteU32(this->version) || + !out->WriteU16(this->numLangs) || + !out->WriteU16(this->searchRange) || + !out->WriteU16(this->entrySelector) || + !out->WriteU16(this->rangeShift) || + !SerializeParts(this->entries, out) || + !SerializeParts(this->settings, out)) { + return Error("Failed to write table"); + } + return true; +} + +bool OpenTypeSILL::LanguageEntry::ParsePart(Buffer& table) { + if (!table.ReadU8(&this->langcode[0]) || + !table.ReadU8(&this->langcode[1]) || + !table.ReadU8(&this->langcode[2]) || + !table.ReadU8(&this->langcode[3])) { + return parent->Error("LanguageEntry: Failed to read langcode"); + } + if (!table.ReadU16(&this->numSettings)) { + return parent->Error("LanguageEntry: Failed to read numSettings"); + } + if (!table.ReadU16(&this->offset)) { + return parent->Error("LanguageEntry: Failed to read offset"); + } + return true; +} + +bool OpenTypeSILL::LanguageEntry::SerializePart(OTSStream* out) const { + if (!out->WriteU8(this->langcode[0]) || + !out->WriteU8(this->langcode[1]) || + !out->WriteU8(this->langcode[2]) || + !out->WriteU8(this->langcode[3]) || + !out->WriteU16(this->numSettings) || + !out->WriteU16(this->offset)) { + return parent->Error("LanguageEntry: Failed to write"); + } + return true; +} + +bool OpenTypeSILL::LangFeatureSetting::ParsePart(Buffer& table) { + OpenTypeFEAT* feat = static_cast( + parent->GetFont()->GetTypedTable(OTS_TAG_FEAT)); + if (!feat) { + return parent->Error("FeatureDefn: Required Feat table is missing"); + } + + if (!table.ReadU32(&this->featureId) || + !feat->IsValidFeatureId(this->featureId)) { + return parent->Error("LangFeatureSetting: Failed to read valid featureId"); + } + if (!table.ReadS16(&this->value)) { + return parent->Error("LangFeatureSetting: Failed to read value"); + } + if (!table.ReadU16(&this->reserved)) { + return parent->Error("LangFeatureSetting: Failed to read reserved"); + } + if (this->reserved != 0) { + parent->Warning("LangFeatureSetting: Nonzero reserved"); + } + return true; +} + +bool OpenTypeSILL::LangFeatureSetting::SerializePart(OTSStream* out) const { + if (!out->WriteU32(this->featureId) || + !out->WriteS16(this->value) || + !out->WriteU16(this->reserved)) { + return parent->Error("LangFeatureSetting: Failed to read reserved"); + } + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/sill.h b/gfx/ots/src/sill.h new file mode 100644 index 000000000..30f9b8d83 --- /dev/null +++ b/gfx/ots/src/sill.h @@ -0,0 +1,53 @@ +// Copyright (c) 2009-2017 The OTS 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_SILL_H_ +#define OTS_SILL_H_ + +#include "ots.h" +#include "graphite.h" + +#include + +namespace ots { + +class OpenTypeSILL : public Table { + public: + explicit OpenTypeSILL(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + struct LanguageEntry : public TablePart { + explicit LanguageEntry(OpenTypeSILL* parent) + : TablePart(parent) { } + bool ParsePart(Buffer &table); + bool SerializePart(OTSStream* out) const; + uint8_t langcode[4]; + uint16_t numSettings; + uint16_t offset; + }; + struct LangFeatureSetting : public TablePart { + explicit LangFeatureSetting(OpenTypeSILL* parent) + : TablePart(parent) { } + bool ParsePart(Buffer &table); + bool SerializePart(OTSStream* out) const; + uint32_t featureId; + int16_t value; + uint16_t reserved; + }; + uint32_t version; + uint16_t numLangs; + uint16_t searchRange; + uint16_t entrySelector; + uint16_t rangeShift; + std::vector entries; + std::vector settings; +}; + +} // namespace ots + +#endif // OTS_SILL_H_ diff --git a/gfx/ots/src/stat.cc b/gfx/ots/src/stat.cc new file mode 100644 index 000000000..9b7828109 --- /dev/null +++ b/gfx/ots/src/stat.cc @@ -0,0 +1,347 @@ +// Copyright (c) 2018 The OTS 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 "stat.h" +#include "name.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeSTAT +// ----------------------------------------------------------------------------- + +bool OpenTypeSTAT::ValidateNameId(uint16_t nameid, bool allowPredefined) { + OpenTypeNAME* name = static_cast( + GetFont()->GetTypedTable(OTS_TAG_NAME)); + + if (!name || !name->IsValidNameId(nameid)) { + Drop("Invalid nameID: %d", nameid); + return false; + } + + if (!allowPredefined && nameid < 26) { + Warning("nameID out of range: %d", nameid); + return true; + } + + if ((nameid >= 26 && nameid <= 255) || nameid >= 32768) { + Warning("nameID out of range: %d", nameid); + return true; + } + + return true; +} + +bool OpenTypeSTAT::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + if (!table.ReadU16(&this->majorVersion) || + !table.ReadU16(&this->minorVersion) || + !table.ReadU16(&this->designAxisSize) || + !table.ReadU16(&this->designAxisCount) || + !table.ReadU32(&this->designAxesOffset) || + !table.ReadU16(&this->axisValueCount) || + !table.ReadU32(&this->offsetToAxisValueOffsets) || + !(this->minorVersion < 1 || table.ReadU16(&this->elidedFallbackNameID))) { + return Drop("Failed to read table header"); + } + if (this->majorVersion != 1) { + return Drop("Unknown table version"); + } + if (this->minorVersion > 2) { + Warning("Unknown minor version, downgrading to 2"); + this->minorVersion = 2; + } + + if (this->designAxisSize < sizeof(AxisRecord)) { + return Drop("Invalid designAxisSize"); + } + + size_t headerEnd = table.offset(); + + if (this->designAxisCount == 0) { + if (this->designAxesOffset != 0) { + Warning("Unexpected non-zero designAxesOffset"); + this->designAxesOffset = 0; + } + } else { + if (this->designAxesOffset < headerEnd || + size_t(this->designAxesOffset) + + size_t(this->designAxisCount) * size_t(this->designAxisSize) > length) { + return Drop("Invalid designAxesOffset"); + } + } + + for (size_t i = 0; i < this->designAxisCount; i++) { + table.set_offset(this->designAxesOffset + i * this->designAxisSize); + this->designAxes.emplace_back(); + auto& axis = this->designAxes[i]; + if (!table.ReadU32(&axis.axisTag) || + !table.ReadU16(&axis.axisNameID) || + !table.ReadU16(&axis.axisOrdering)) { + return Drop("Failed to read design axis"); + } + if (!CheckTag(axis.axisTag)) { + return Drop("Bad design axis tag"); + } + if (!ValidateNameId(axis.axisNameID, false)) { + return true; + } + } + + // TODO + // - check that all axes defined in fvar are covered by STAT + // - check that axisOrdering values are not duplicated (warn only) + + if (this->axisValueCount == 0) { + if (this->offsetToAxisValueOffsets != 0) { + Warning("Unexpected non-zero offsetToAxisValueOffsets"); + this->offsetToAxisValueOffsets = 0; + } + } else { + if (this->offsetToAxisValueOffsets < headerEnd || + size_t(this->offsetToAxisValueOffsets) + + size_t(this->axisValueCount) * sizeof(uint16_t) > length) { + return Drop("Invalid offsetToAxisValueOffsets"); + } + } + + for (size_t i = 0; i < this->axisValueCount; i++) { + table.set_offset(this->offsetToAxisValueOffsets + i * sizeof(uint16_t)); + uint16_t axisValueOffset; + if (!table.ReadU16(&axisValueOffset)) { + return Drop("Failed to read axis value offset"); + } + if (this->offsetToAxisValueOffsets + axisValueOffset > length) { + return Drop("Invalid axis value offset"); + } + table.set_offset(this->offsetToAxisValueOffsets + axisValueOffset); + uint16_t format; + if (!table.ReadU16(&format)) { + return Drop("Failed to read axis value format"); + } + this->axisValues.emplace_back(format); + auto& axisValue = axisValues[i]; + switch (format) { + case 1: + if (!table.ReadU16(&axisValue.format1.axisIndex) || + !table.ReadU16(&axisValue.format1.flags) || + !table.ReadU16(&axisValue.format1.valueNameID) || + !table.ReadS32(&axisValue.format1.value)) { + return Drop("Failed to read axis value (format 1)"); + } + if (axisValue.format1.axisIndex >= this->designAxisCount) { + return Drop("Axis index out of range"); + } + if ((axisValue.format1.flags & 0xFFFCu) != 0) { + Warning("Unexpected axis value flags"); + axisValue.format1.flags &= ~0xFFFCu; + } + if (!ValidateNameId(axisValue.format1.valueNameID)) { + return true; + } + break; + case 2: + if (!table.ReadU16(&axisValue.format2.axisIndex) || + !table.ReadU16(&axisValue.format2.flags) || + !table.ReadU16(&axisValue.format2.valueNameID) || + !table.ReadS32(&axisValue.format2.nominalValue) || + !table.ReadS32(&axisValue.format2.rangeMinValue) || + !table.ReadS32(&axisValue.format2.rangeMaxValue)) { + return Drop("Failed to read axis value (format 2)"); + } + if (axisValue.format2.axisIndex >= this->designAxisCount) { + return Drop("Axis index out of range"); + } + if ((axisValue.format2.flags & 0xFFFCu) != 0) { + Warning("Unexpected axis value flags"); + axisValue.format1.flags &= ~0xFFFCu; + } + if (!ValidateNameId(axisValue.format2.valueNameID)) { + return true; + } + if (!(axisValue.format2.rangeMinValue <= axisValue.format2.nominalValue && + axisValue.format2.nominalValue <= axisValue.format2.rangeMaxValue)) { + Warning("Bad axis value range or nominal value"); + } + break; + case 3: + if (!table.ReadU16(&axisValue.format3.axisIndex) || + !table.ReadU16(&axisValue.format3.flags) || + !table.ReadU16(&axisValue.format3.valueNameID) || + !table.ReadS32(&axisValue.format3.value) || + !table.ReadS32(&axisValue.format3.linkedValue)) { + return Drop("Failed to read axis value (format 3)"); + } + if (axisValue.format3.axisIndex >= this->designAxisCount) { + return Drop("Axis index out of range"); + } + if ((axisValue.format3.flags & 0xFFFCu) != 0) { + Warning("Unexpected axis value flags"); + axisValue.format3.flags &= ~0xFFFCu; + } + if (!ValidateNameId(axisValue.format3.valueNameID)) { + return true; + } + break; + case 4: + if (this->minorVersion < 2) { + Warning("Invalid table version for format 4 axis values - updating"); + this->minorVersion = 2; + } + if (!table.ReadU16(&axisValue.format4.axisCount) || + !table.ReadU16(&axisValue.format4.flags) || + !table.ReadU16(&axisValue.format4.valueNameID)) { + return Drop("Failed to read axis value (format 4)"); + } + if (axisValue.format4.axisCount > this->designAxisCount) { + return Drop("Axis count out of range"); + } + if ((axisValue.format4.flags & 0xFFFCu) != 0) { + Warning("Unexpected axis value flags"); + axisValue.format4.flags &= ~0xFFFCu; + } + if (!ValidateNameId(axisValue.format4.valueNameID)) { + return true; + } + for (unsigned j = 0; j < axisValue.format4.axisCount; j++) { + axisValue.format4.axisValues.emplace_back(); + auto& v = axisValue.format4.axisValues[j]; + if (!table.ReadU16(&v.axisIndex) || + !table.ReadS32(&v.value)) { + return Drop("Failed to read axis value"); + } + if (v.axisIndex >= this->designAxisCount) { + return Drop("Axis index out of range"); + } + } + break; + default: + return Drop("Unknown axis value format"); + } + } + + return true; +} + +bool OpenTypeSTAT::Serialize(OTSStream* out) { + off_t tableStart = out->Tell(); + + size_t headerSize = 5 * sizeof(uint16_t) + 2 * sizeof(uint32_t); + if (this->minorVersion >= 1) { + headerSize += sizeof(uint16_t); + } + + if (this->designAxisCount == 0) { + this->designAxesOffset = 0; + } else { + this->designAxesOffset = headerSize; + } + + this->designAxisSize = sizeof(AxisRecord); + + if (this->axisValueCount == 0) { + this->offsetToAxisValueOffsets = 0; + } else { + if (this->designAxesOffset == 0) { + this->offsetToAxisValueOffsets = headerSize; + } else { + this->offsetToAxisValueOffsets = this->designAxesOffset + this->designAxisCount * this->designAxisSize; + } + } + + if (!out->WriteU16(this->majorVersion) || + !out->WriteU16(this->minorVersion) || + !out->WriteU16(this->designAxisSize) || + !out->WriteU16(this->designAxisCount) || + !out->WriteU32(this->designAxesOffset) || + !out->WriteU16(this->axisValueCount) || + !out->WriteU32(this->offsetToAxisValueOffsets) || + !(this->minorVersion < 1 || out->WriteU16(this->elidedFallbackNameID))) { + return Error("Failed to write table header"); + } + + if (this->designAxisCount > 0) { + if (out->Tell() - tableStart != this->designAxesOffset) { + return Error("Error computing designAxesOffset"); + } + } + + for (unsigned i = 0; i < this->designAxisCount; i++) { + const auto& axis = this->designAxes[i]; + if (!out->WriteU32(axis.axisTag) || + !out->WriteU16(axis.axisNameID) || + !out->WriteU16(axis.axisOrdering)) { + return Error("Failed to write design axis"); + } + } + + if (this->axisValueCount > 0) { + if (out->Tell() - tableStart != this->offsetToAxisValueOffsets) { + return Error("Error computing offsetToAxisValueOffsets"); + } + } + + uint32_t axisValueOffset = this->axisValueCount * sizeof(uint16_t); + for (unsigned i = 0; i < this->axisValueCount; i++) { + const auto& value = this->axisValues[i]; + if (!out->WriteU16(axisValueOffset)) { + return Error("Failed to write axis value offset"); + } + axisValueOffset += value.Length(); + } + for (unsigned i = 0; i < this->axisValueCount; i++) { + const auto& value = this->axisValues[i]; + if (!out->WriteU16(value.format)) { + return Error("Failed to write axis value"); + } + switch (value.format) { + case 1: + if (!out->WriteU16(value.format1.axisIndex) || + !out->WriteU16(value.format1.flags) || + !out->WriteU16(value.format1.valueNameID) || + !out->WriteS32(value.format1.value)) { + return Error("Failed to write axis value"); + } + break; + case 2: + if (!out->WriteU16(value.format2.axisIndex) || + !out->WriteU16(value.format2.flags) || + !out->WriteU16(value.format2.valueNameID) || + !out->WriteS32(value.format2.nominalValue) || + !out->WriteS32(value.format2.rangeMinValue) || + !out->WriteS32(value.format2.rangeMaxValue)) { + return Error("Failed to write axis value"); + } + break; + case 3: + if (!out->WriteU16(value.format3.axisIndex) || + !out->WriteU16(value.format3.flags) || + !out->WriteU16(value.format3.valueNameID) || + !out->WriteS32(value.format3.value) || + !out->WriteS32(value.format3.linkedValue)) { + return Error("Failed to write axis value"); + } + break; + case 4: + if (!out->WriteU16(value.format4.axisCount) || + !out->WriteU16(value.format4.flags) || + !out->WriteU16(value.format4.valueNameID)) { + return Error("Failed to write axis value"); + } + for (unsigned j = 0; j < value.format4.axisValues.size(); j++) { + if (!out->WriteU16(value.format4.axisValues[j].axisIndex) || + !out->WriteS32(value.format4.axisValues[j].value)) { + return Error("Failed to write axis value"); + } + } + break; + default: + return Error("Bad value format"); + } + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/stat.h b/gfx/ots/src/stat.h new file mode 100644 index 000000000..873d08edc --- /dev/null +++ b/gfx/ots/src/stat.h @@ -0,0 +1,155 @@ +// Copyright (c) 2018 The OTS 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_STAT_H_ +#define OTS_STAT_H_ + +#include + +#include "ots.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeSTAT Interface +// ----------------------------------------------------------------------------- + +class OpenTypeSTAT : public Table { + public: + explicit OpenTypeSTAT(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + bool ValidateNameId(uint16_t nameid, bool allowPredefined = true); + + uint16_t majorVersion; + uint16_t minorVersion; + uint16_t designAxisSize; + uint16_t designAxisCount; + uint32_t designAxesOffset; + uint16_t axisValueCount; + uint32_t offsetToAxisValueOffsets; + uint16_t elidedFallbackNameID; + + struct AxisRecord { + uint32_t axisTag; + uint16_t axisNameID; + uint16_t axisOrdering; + }; + std::vector designAxes; + + typedef int32_t Fixed; /* 16.16 fixed-point value */ + + struct AxisValueFormat1 { + uint16_t axisIndex; + uint16_t flags; + uint16_t valueNameID; + Fixed value; + static size_t Length() { + return 3 * sizeof(uint16_t) + sizeof(Fixed); + } + }; + + struct AxisValueFormat2 { + uint16_t axisIndex; + uint16_t flags; + uint16_t valueNameID; + Fixed nominalValue; + Fixed rangeMinValue; + Fixed rangeMaxValue; + static size_t Length() { + return 3 * sizeof(uint16_t) + 3 * sizeof(Fixed); + } + }; + + struct AxisValueFormat3 { + uint16_t axisIndex; + uint16_t flags; + uint16_t valueNameID; + Fixed value; + Fixed linkedValue; + static size_t Length() { + return 3 * sizeof(uint16_t) + 2 * sizeof(Fixed); + } + }; + + struct AxisValueFormat4 { + uint16_t axisCount; + uint16_t flags; + uint16_t valueNameID; + struct AxisValue { + uint16_t axisIndex; + Fixed value; + }; + std::vector axisValues; + size_t Length() const { + return 3 * sizeof(uint16_t) + axisValues.size() * (sizeof(uint16_t) + sizeof(Fixed)); + } + }; + + struct AxisValueRecord { + uint16_t format; + union { + AxisValueFormat1 format1; + AxisValueFormat2 format2; + AxisValueFormat3 format3; + AxisValueFormat4 format4; + }; + explicit AxisValueRecord(uint16_t format_) + : format(format_) + { + if (format == 4) { + new (&this->format4) AxisValueFormat4(); + } + } + AxisValueRecord(const AxisValueRecord& other_) + : format(other_.format) + { + switch (format) { + case 1: + format1 = other_.format1; + break; + case 2: + format2 = other_.format2; + break; + case 3: + format3 = other_.format3; + break; + case 4: + new (&this->format4) AxisValueFormat4(); + format4 = other_.format4; + break; + } + } + ~AxisValueRecord() { + if (format == 4) { + this->format4.~AxisValueFormat4(); + } + } + uint32_t Length() const { + switch (format) { + case 1: + return sizeof(uint16_t) + format1.Length(); + case 2: + return sizeof(uint16_t) + format2.Length(); + case 3: + return sizeof(uint16_t) + format3.Length(); + case 4: + return sizeof(uint16_t) + format4.Length(); + default: + // can't happen + return 0; + } + } + }; + + std::vector axisValues; +}; + +} // namespace ots + +#endif // OTS_STAT_H_ diff --git a/gfx/ots/src/variations.cc b/gfx/ots/src/variations.cc new file mode 100644 index 000000000..cb7e5fb32 --- /dev/null +++ b/gfx/ots/src/variations.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2018 The OTS 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 "fvar.h" + +// OpenType Variations Common Table Formats + +#define TABLE_NAME "Variations" // XXX: use individual table names + +namespace { + +bool ParseVariationRegionList(const ots::Font* font, const uint8_t* data, const size_t length, + uint16_t* regionCount) { + ots::Buffer subtable(data, length); + + uint16_t axisCount; + + if (!subtable.ReadU16(&axisCount) || + !subtable.ReadU16(regionCount)) { + return OTS_FAILURE_MSG("Failed to read variation region list header"); + } + + if (*regionCount == 0) { + return true; + } + + const ots::OpenTypeFVAR* fvar = + static_cast(font->GetTypedTable(OTS_TAG_FVAR)); + if (!fvar) { + return OTS_FAILURE_MSG("Required fvar table is missing"); + } + if (axisCount != fvar->AxisCount()) { + return OTS_FAILURE_MSG("Axis count mismatch"); + } + + for (unsigned i = 0; i < *regionCount; i++) { + for (unsigned j = 0; j < axisCount; j++) { + int16_t startCoord, peakCoord, endCoord; + if (!subtable.ReadS16(&startCoord) || + !subtable.ReadS16(&peakCoord) || + !subtable.ReadS16(&endCoord)) { + return OTS_FAILURE_MSG("Failed to read region axis coordinates"); + } + if (startCoord > peakCoord || peakCoord > endCoord) { + return OTS_FAILURE_MSG("Region axis coordinates out of order"); + } + if (startCoord < -0x4000 || endCoord > 0x4000) { + return OTS_FAILURE_MSG("Region axis coordinate out of range"); + } + if ((peakCoord < 0 && endCoord > 0) || + (peakCoord > 0 && startCoord < 0)) { + return OTS_FAILURE_MSG("Invalid region axis coordinates"); + } + } + } + + return true; +} + +bool +ParseVariationDataSubtable(const ots::Font* font, const uint8_t* data, const size_t length, + const uint16_t regionCount, + uint16_t* regionIndexCount) { + ots::Buffer subtable(data, length); + + uint16_t itemCount; + uint16_t shortDeltaCount; + + if (!subtable.ReadU16(&itemCount) || + !subtable.ReadU16(&shortDeltaCount) || + !subtable.ReadU16(regionIndexCount)) { + return OTS_FAILURE_MSG("Failed to read variation data subtable header"); + } + + for (unsigned i = 0; i < *regionIndexCount; i++) { + uint16_t regionIndex; + if (!subtable.ReadU16(®ionIndex) || regionIndex >= regionCount) { + return OTS_FAILURE_MSG("Bad region index"); + } + } + + if (!subtable.Skip(size_t(itemCount) * (size_t(shortDeltaCount) + size_t(*regionIndexCount)))) { + return OTS_FAILURE_MSG("Failed to read delta data"); + } + + return true; +} + +} // namespace + +namespace ots { + +bool +ParseItemVariationStore(const Font* font, + const uint8_t* data, const size_t length, + std::vector* regionIndexCounts) { + Buffer subtable(data, length); + + uint16_t format; + uint32_t variationRegionListOffset; + uint16_t itemVariationDataCount; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU32(&variationRegionListOffset) || + !subtable.ReadU16(&itemVariationDataCount)) { + return OTS_FAILURE_MSG("Failed to read item variation store header"); + } + + if (format != 1) { + return OTS_FAILURE_MSG("Unknown item variation store format"); + } + + if (variationRegionListOffset < subtable.offset() + 4 * itemVariationDataCount || + variationRegionListOffset > length) { + return OTS_FAILURE_MSG("Invalid variation region list offset"); + } + + uint16_t regionCount; + if (!ParseVariationRegionList(font, + data + variationRegionListOffset, + length - variationRegionListOffset, + ®ionCount)) { + return OTS_FAILURE_MSG("Failed to parse variation region list"); + } + + for (unsigned i = 0; i < itemVariationDataCount; i++) { + uint32_t offset; + if (!subtable.ReadU32(&offset)) { + return OTS_FAILURE_MSG("Failed to read variation data subtable offset"); + } + if (offset >= length) { + return OTS_FAILURE_MSG("Bad offset to variation data subtable"); + } + uint16_t regionIndexCount = 0; + if (!ParseVariationDataSubtable(font, data + offset, length - offset, + regionCount, + ®ionIndexCount)) { + return OTS_FAILURE_MSG("Failed to parse variation data subtable"); + } + if (regionIndexCounts) { + regionIndexCounts->push_back(regionIndexCount); + } + } + + return true; +} + +bool ParseDeltaSetIndexMap(const Font* font, const uint8_t* data, const size_t length) { + Buffer subtable(data, length); + + uint16_t entryFormat; + uint16_t mapCount; + + if (!subtable.ReadU16(&entryFormat) || + !subtable.ReadU16(&mapCount)) { + return OTS_FAILURE_MSG("Failed to read delta set index map header"); + } + + const uint16_t MAP_ENTRY_SIZE_MASK = 0x0030; + + const uint16_t entrySize = (((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1); + if (!subtable.Skip(entrySize * mapCount)) { + return OTS_FAILURE_MSG("Failed to read delta set index map data"); + } + + return true; +} + +bool ParseVariationData(const Font* font, const uint8_t* data, size_t length, + size_t axisCount, size_t sharedTupleCount) { + Buffer subtable(data, length); + + uint16_t tupleVariationCount; + uint16_t dataOffset; + if (!subtable.ReadU16(&tupleVariationCount) || + !subtable.ReadU16(&dataOffset)) { + return OTS_FAILURE_MSG("Failed to read variation data header"); + } + + if (dataOffset > length) { + return OTS_FAILURE_MSG("Invalid serialized data offset"); + } + + tupleVariationCount &= 0x0FFF; // mask off flags + + const uint16_t EMBEDDED_PEAK_TUPLE = 0x8000; + const uint16_t INTERMEDIATE_REGION = 0x4000; + const uint16_t TUPLE_INDEX_MASK = 0x0FFF; + + for (unsigned i = 0; i < tupleVariationCount; i++) { + uint16_t variationDataSize; + uint16_t tupleIndex; + + if (!subtable.ReadU16(&variationDataSize) || + !subtable.ReadU16(&tupleIndex)) { + return OTS_FAILURE_MSG("Failed to read tuple variation header"); + } + + if (tupleIndex & EMBEDDED_PEAK_TUPLE) { + for (unsigned axis = 0; axis < axisCount; axis++) { + int16_t coordinate; + if (!subtable.ReadS16(&coordinate)) { + return OTS_FAILURE_MSG("Failed to read tuple coordinate"); + } + if (coordinate < -0x4000 || coordinate > 0x4000) { + return OTS_FAILURE_MSG("Invalid tuple coordinate"); + } + } + } + + if (tupleIndex & INTERMEDIATE_REGION) { + std::vector startTuple(axisCount); + for (unsigned axis = 0; axis < axisCount; axis++) { + int16_t coordinate; + if (!subtable.ReadS16(&coordinate)) { + return OTS_FAILURE_MSG("Failed to read tuple coordinate"); + } + if (coordinate < -0x4000 || coordinate > 0x4000) { + return OTS_FAILURE_MSG("Invalid tuple coordinate"); + } + startTuple.push_back(coordinate); + } + + std::vector endTuple(axisCount); + for (unsigned axis = 0; axis < axisCount; axis++) { + int16_t coordinate; + if (!subtable.ReadS16(&coordinate)) { + return OTS_FAILURE_MSG("Failed to read tuple coordinate"); + } + if (coordinate < -0x4000 || coordinate > 0x4000) { + return OTS_FAILURE_MSG("Invalid tuple coordinate"); + } + endTuple.push_back(coordinate); + } + + for (unsigned axis = 0; axis < axisCount; axis++) { + if (startTuple[axis] > endTuple[axis]) { + return OTS_FAILURE_MSG("Invalid intermediate range"); + } + } + } + + if (!(tupleIndex & EMBEDDED_PEAK_TUPLE)) { + tupleIndex &= TUPLE_INDEX_MASK; + if (tupleIndex >= sharedTupleCount) { + return OTS_FAILURE_MSG("Tuple index out of range"); + } + } + } + + // TODO: we don't attempt to interpret the serialized data block + + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/variations.h b/gfx/ots/src/variations.h new file mode 100644 index 000000000..aaaac1784 --- /dev/null +++ b/gfx/ots/src/variations.h @@ -0,0 +1,26 @@ +// Copyright (c) 2018 The OTS 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_VARIATIONS_H_ +#define OTS_VARIATIONS_H_ + +#include +#include "ots.h" + +// Utility functions for OpenType variations common table formats. + +namespace ots { + +bool ParseItemVariationStore(const Font* font, + const uint8_t* data, const size_t length, + std::vector* out_region_index_count = NULL); + +bool ParseDeltaSetIndexMap(const Font* font, const uint8_t* data, const size_t length); + +bool ParseVariationData(const Font* font, const uint8_t* data, size_t length, + size_t axisCount, size_t sharedTupleCount); + +} // namespace ots + +#endif // OTS_VARIATIONS_H_ diff --git a/gfx/ots/src/vdmx.cc b/gfx/ots/src/vdmx.cc index cd80946ae..54055777a 100644 --- a/gfx/ots/src/vdmx.cc +++ b/gfx/ots/src/vdmx.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,91 +7,75 @@ // 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) { +bool OpenTypeVDMX::Parse(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 (!table.ReadU16(&this->version) || + !table.ReadU16(&this->num_recs) || + !table.ReadU16(&this->num_ratios)) { + return Error("Failed to read table header"); } - if (vdmx->version > 1) { - DROP_THIS_TABLE("bad version: %u", vdmx->version); - return true; // continue transcoding + if (this->version > 1) { + return Drop("Unsupported table version: %u", this->version); } - vdmx->rat_ranges.reserve(vdmx->num_ratios); - for (unsigned i = 0; i < vdmx->num_ratios; ++i) { + this->rat_ranges.reserve(this->num_ratios); + for (unsigned i = 0; i < this->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); + return Error("Failed to read RatioRange record %d", i); } if (rec.charset > 1) { - DROP_THIS_TABLE("bad charset: %u", rec.charset); - return true; + return Drop("Unsupported character set: %u", rec.charset); } if (rec.y_start_ratio > rec.y_end_ratio) { - DROP_THIS_TABLE("bad y ratio"); - return true; + return Drop("Bad y ratio"); } // 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) && + if ((i < this->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; + return Drop("Superfluous terminator found"); } - vdmx->rat_ranges.push_back(rec); + this->rat_ranges.push_back(rec); } - vdmx->offsets.reserve(vdmx->num_ratios); + this->offsets.reserve(this->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) { + for (unsigned i = 0; i < this->num_ratios; ++i) { uint16_t offset; if (!table.ReadU16(&offset)) { - return OTS_FAILURE_MSG("Failed to read ratio offset %d", i); + return Error("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); + return Error("Bad ratio offset %d for ration %d", offset, i); } - vdmx->offsets.push_back(offset); + this->offsets.push_back(offset); } - vdmx->groups.reserve(vdmx->num_recs); - for (unsigned i = 0; i < vdmx->num_recs; ++i) { + this->groups.reserve(this->num_recs); + for (unsigned i = 0; i < this->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); + return Error("Failed to read record header %d", i); } group.entries.reserve(group.recs); for (unsigned j = 0; j < group.recs; ++j) { @@ -99,71 +83,68 @@ bool ots_vdmx_parse(Font *font, const uint8_t *data, size_t length) { 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); + return Error("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; + return Drop("bad y min/max"); } // 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; + return Drop("The table is not sorted"); } group.entries.push_back(vt); } - vdmx->groups.push_back(group); + this->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 OpenTypeVDMX::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != 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"); +bool OpenTypeVDMX::Serialize(OTSStream *out) { + if (!out->WriteU16(this->version) || + !out->WriteU16(this->num_recs) || + !out->WriteU16(this->num_ratios)) { + return Error("Failed to write table header"); } - for (unsigned i = 0; i < vdmx->rat_ranges.size(); ++i) { - const OpenTypeVDMXRatioRecord& rec = vdmx->rat_ranges[i]; + for (unsigned i = 0; i < this->rat_ranges.size(); ++i) { + const OpenTypeVDMXRatioRecord& rec = this->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); + return Error("Failed to write RatioRange record %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 < this->offsets.size(); ++i) { + if (!out->WriteU16(this->offsets[i])) { + return Error("Failed to write ratio offset %d", i); } } - for (unsigned i = 0; i < vdmx->groups.size(); ++i) { - const OpenTypeVDMXGroup& group = vdmx->groups[i]; + for (unsigned i = 0; i < this->groups.size(); ++i) { + const OpenTypeVDMXGroup& group = this->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); + return Error("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 Error("Failed to write group %d entry %d", i, j); } } } @@ -171,16 +152,4 @@ bool ots_vdmx_serialise(OTSStream *out, Font *font) { 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 index 1d959efe5..6ccf6dc1b 100644 --- a/gfx/ots/src/vdmx.h +++ b/gfx/ots/src/vdmx.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -31,7 +31,16 @@ struct OpenTypeVDMXGroup { std::vector entries; }; -struct OpenTypeVDMX { +class OpenTypeVDMX : public Table { + public: + explicit OpenTypeVDMX(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); + + private: uint16_t version; uint16_t num_recs; uint16_t num_ratios; diff --git a/gfx/ots/src/vhea.cc b/gfx/ots/src/vhea.cc index e721b971e..e44bedf20 100644 --- a/gfx/ots/src/vhea.cc +++ b/gfx/ots/src/vhea.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,51 +10,30 @@ // 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) { +bool OpenTypeVHEA::Parse(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 (!table.ReadU32(&this->version)) { + return Error("Failed to read version"); } - - if (!ParseMetricsHeader(font, &table, &vhea->header)) { - return OTS_FAILURE_MSG("Failed to parse metrics in vhea"); + if (this->version != 0x00010000 && + this->version != 0x00011000) { + return Error("Unsupported table version: 0x%x", this->version); } - return true; + return OpenTypeMetricsHeader::Parse(data, length); } -bool ots_vhea_should_serialise(Font *font) { - // vhea should'nt serialise when vmtx doesn't exist. - return font->vhea != NULL && font->vmtx != NULL; +bool OpenTypeVHEA::Serialize(OTSStream *out) { + return OpenTypeMetricsHeader::Serialize(out); } -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; +bool OpenTypeVHEA::ShouldSerialize() { + return OpenTypeMetricsHeader::ShouldSerialize() && + // vhea shouldn't serialise when vmtx doesn't exist. + GetFont()->GetTable(OTS_TAG_VMTX) != NULL; } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/vhea.h b/gfx/ots/src/vhea.h index f8efde731..8776acd11 100644 --- a/gfx/ots/src/vhea.h +++ b/gfx/ots/src/vhea.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,8 +10,14 @@ namespace ots { -struct OpenTypeVHEA { - OpenTypeMetricsHeader header; +class OpenTypeVHEA : public OpenTypeMetricsHeader { + public: + explicit OpenTypeVHEA(Font *font, uint32_t tag) + : OpenTypeMetricsHeader(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); }; } // namespace ots diff --git a/gfx/ots/src/vmtx.cc b/gfx/ots/src/vmtx.cc index 64a706148..d576ae294 100644 --- a/gfx/ots/src/vmtx.cc +++ b/gfx/ots/src/vmtx.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -10,48 +10,20 @@ // 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 OpenTypeVMTX::Parse(const uint8_t *data, size_t length) { + return OpenTypeMetricsTable::Parse(data, length); } -bool ots_vmtx_should_serialise(Font *font) { - // vmtx should serialise when vhea is preserved. - return font->vmtx != NULL && font->vhea != NULL; +bool OpenTypeVMTX::Serialize(OTSStream *out) { + return OpenTypeMetricsTable::Serialize(out); } -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; +bool OpenTypeVMTX::ShouldSerialize() { + return OpenTypeMetricsTable::ShouldSerialize() && + // vmtx should serialise when vhea is preserved. + GetFont()->GetTable(OTS_TAG_VHEA) != NULL; } } // namespace ots - -#undef TABLE_NAME diff --git a/gfx/ots/src/vmtx.h b/gfx/ots/src/vmtx.h index 061dc73ed..b243fe214 100644 --- a/gfx/ots/src/vmtx.h +++ b/gfx/ots/src/vmtx.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -6,12 +6,19 @@ #define OTS_VMTX_H_ #include "metrics.h" +#include "vhea.h" #include "ots.h" namespace ots { -struct OpenTypeVMTX { - OpenTypeMetricsTable metrics; +struct OpenTypeVMTX : public OpenTypeMetricsTable { + public: + explicit OpenTypeVMTX(Font *font, uint32_t tag) + : OpenTypeMetricsTable(font, tag, tag, OTS_TAG_VHEA) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); }; } // namespace ots diff --git a/gfx/ots/src/vorg.cc b/gfx/ots/src/vorg.cc index 358923125..3b4b51c53 100644 --- a/gfx/ots/src/vorg.cc +++ b/gfx/ots/src/vorg.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -9,37 +9,23 @@ // 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) { +bool OpenTypeVORG::Parse(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) || + if (!table.ReadU16(&this->major_version) || + !table.ReadU16(&this->minor_version) || + !table.ReadS16(&this->default_vert_origin_y) || !table.ReadU16(&num_recs)) { - return OTS_FAILURE_MSG("Failed to read header"); + return Error("Failed to read header"); } - if (vorg->major_version != 1) { - DROP_THIS_TABLE("bad major version: %u", vorg->major_version); - return true; + if (this->major_version != 1) { + return Drop("Unsupported majorVersion: %u", this->major_version); } - if (vorg->minor_version != 0) { - DROP_THIS_TABLE("bad minor version: %u", vorg->minor_version); - return true; + if (this->minor_version != 0) { + return Drop("Unsupported minorVersion: %u", this->minor_version); } // num_recs might be zero (e.g., DFHSMinchoPro5-W3-Demo.otf). @@ -48,64 +34,50 @@ bool ots_vorg_parse(Font *font, const uint8_t *data, size_t length) { } uint16_t last_glyph_index = 0; - vorg->metrics.reserve(num_recs); + this->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); + return Error("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; + return Drop("The table is not sorted"); } last_glyph_index = rec.glyph_index; - vorg->metrics.push_back(rec); + this->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(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) || +bool OpenTypeVORG::Serialize(OTSStream *out) { + const uint16_t num_metrics = static_cast(this->metrics.size()); + if (num_metrics != this->metrics.size() || + !out->WriteU16(this->major_version) || + !out->WriteU16(this->minor_version) || + !out->WriteS16(this->default_vert_origin_y) || !out->WriteU16(num_metrics)) { - return OTS_FAILURE_MSG("Failed to write table header"); + return Error("Failed to write table header"); } for (uint16_t i = 0; i < num_metrics; ++i) { - const OpenTypeVORGMetrics& rec = vorg->metrics[i]; + const OpenTypeVORGMetrics& rec = this->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 Error("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; +bool OpenTypeVORG::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for fonts with TT glyphs. + GetFont()->GetTable(OTS_TAG_CFF) != NULL; } } // namespace ots - -#undef TABLE_NAME -#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/vorg.h b/gfx/ots/src/vorg.h index c3d3ffdae..caffea875 100644 --- a/gfx/ots/src/vorg.h +++ b/gfx/ots/src/vorg.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2009-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -16,7 +16,16 @@ struct OpenTypeVORGMetrics { int16_t vert_origin_y; }; -struct OpenTypeVORG { +class OpenTypeVORG : public Table { + public: + explicit OpenTypeVORG(Font *font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool ShouldSerialize(); + + private: uint16_t major_version; uint16_t minor_version; int16_t default_vert_origin_y; diff --git a/gfx/ots/src/vvar.cc b/gfx/ots/src/vvar.cc new file mode 100644 index 000000000..b47ea479d --- /dev/null +++ b/gfx/ots/src/vvar.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2018 The OTS 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 "vvar.h" + +#include "variations.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeVVAR +// ----------------------------------------------------------------------------- + +bool OpenTypeVVAR::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + + uint16_t majorVersion; + uint16_t minorVersion; + uint32_t itemVariationStoreOffset; + uint32_t advanceHeightMappingOffset; + uint32_t tsbMappingOffset; + uint32_t bsbMappingOffset; + uint32_t vOrgMappingOffset; + + if (!table.ReadU16(&majorVersion) || + !table.ReadU16(&minorVersion) || + !table.ReadU32(&itemVariationStoreOffset) || + !table.ReadU32(&advanceHeightMappingOffset) || + !table.ReadU32(&tsbMappingOffset) || + !table.ReadU32(&bsbMappingOffset) || + !table.ReadU32(&vOrgMappingOffset)) { + return DropVariations("Failed to read table header"); + } + + if (majorVersion != 1) { + return DropVariations("Unknown table version"); + } + + if (itemVariationStoreOffset > length || + advanceHeightMappingOffset > length || + tsbMappingOffset > length || + bsbMappingOffset > length || + vOrgMappingOffset > length) { + return DropVariations("Invalid subtable offset"); + } + + if (!ParseItemVariationStore(GetFont(), data + itemVariationStoreOffset, + length - itemVariationStoreOffset)) { + return DropVariations("Failed to parse item variation store"); + } + + if (advanceHeightMappingOffset) { + if (!ParseDeltaSetIndexMap(GetFont(), data + advanceHeightMappingOffset, + length - advanceHeightMappingOffset)) { + return DropVariations("Failed to parse advance height mappings"); + } + } + + if (tsbMappingOffset) { + if (!ParseDeltaSetIndexMap(GetFont(), data + tsbMappingOffset, + length - tsbMappingOffset)) { + return DropVariations("Failed to parse TSB mappings"); + } + } + + if (bsbMappingOffset) { + if (!ParseDeltaSetIndexMap(GetFont(), data + bsbMappingOffset, + length - bsbMappingOffset)) { + return DropVariations("Failed to parse BSB mappings"); + } + } + + if (vOrgMappingOffset) { + if (!ParseDeltaSetIndexMap(GetFont(), data + vOrgMappingOffset, + length - vOrgMappingOffset)) { + return DropVariations("Failed to parse vOrg mappings"); + } + } + + this->m_data = data; + this->m_length = length; + + return true; +} + +bool OpenTypeVVAR::Serialize(OTSStream* out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write VVAR table"); + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/vvar.h b/gfx/ots/src/vvar.h new file mode 100644 index 000000000..15d435745 --- /dev/null +++ b/gfx/ots/src/vvar.h @@ -0,0 +1,31 @@ +// Copyright (c) 2018 The OTS 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_VVAR_H_ +#define OTS_VVAR_H_ + +#include "ots.h" + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeVVAR Interface +// ----------------------------------------------------------------------------- + +class OpenTypeVVAR : public Table { + public: + explicit OpenTypeVVAR(Font* font, uint32_t tag) + : Table(font, tag, tag) { } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + + private: + const uint8_t *m_data; + size_t m_length; +}; + +} // namespace ots + +#endif // OTS_VVAR_H_ diff --git a/gfx/ots/sync.sh b/gfx/ots/sync.sh index 5a7e54b0a..81738f9e1 100755 --- a/gfx/ots/sync.sh +++ b/gfx/ots/sync.sh @@ -20,10 +20,19 @@ echo "Updating include..." rm -rf include/ cp -r $1/include . +echo "Updating tests..." +rm -rf tests/* +mkdir -p tests +cp -r $1/tests/*.cc tests + echo "Updating README.mozilla..." REVISION=`cd $1; git log | head -1 | sed "s/commit //"` -sed -e "s/\(Current revision: \).*/\1$REVISION/" README.mozilla > README.tmp +VERSION=`cd $1; git describe | cut -d '-' -f 1 | sed 's/v//'` +sed -e "s/\(Current revision: \).*/\1$REVISION \($VERSION\)/" README.mozilla > README.tmp mv README.tmp README.mozilla echo "Applying ots-visibility.patch..." patch -p3 < ots-visibility.patch + +echo "Applying ots-lz4.patch..." +patch -p3 < ots-lz4.patch diff --git a/gfx/ots/tests/cff_charstring_test.cc b/gfx/ots/tests/cff_charstring_test.cc new file mode 100644 index 000000000..18e077e8f --- /dev/null +++ b/gfx/ots/tests/cff_charstring_test.cc @@ -0,0 +1,1588 @@ +// Copyright (c) 2010-2017 The OTS 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_charstring.h" + +#include + +#include +#include + +#include "cff.h" + +// Returns a biased number for callsubr and callgsubr operators. +#define GET_SUBR_NUMBER(n) ((n) - 107) +#define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0])) + +namespace { + +// A constant which is used in AddSubr function below. +const int kOpPrefix = INT_MAX; + +// Encodes an operator |op| to 1 or more bytes and pushes them to |out_bytes|. +// Returns true if the conversion succeeds. +bool EncodeOperator(int op, std::vector *out_bytes) { + if (op < 0) { + return false; + } + if (op <= 11) { + out_bytes->push_back(op); + return true; + } + if (op == 12) { + return false; + } + if (op <= 27) { + out_bytes->push_back(op); + return true; + } + if (op == 28) { + return false; + } + if (op <= 31) { + out_bytes->push_back(op); + return true; + } + + const uint8_t upper = (op & 0xff00u) >> 8; + const uint8_t lower = op & 0xffu; + if (upper != 12) { + return false; + } + out_bytes->push_back(upper); + out_bytes->push_back(lower); + return true; +} + +// Encodes a number |num| to 1 or more bytes and pushes them to |out_bytes|. +// Returns true if the conversion succeeds. The function does not support 16.16 +// Fixed number. +bool EncodeNumber(int num, std::vector *out_bytes) { + if (num >= -107 && num <= 107) { + out_bytes->push_back(num + 139); + return true; + } + if (num >= 108 && num <= 1131) { + const uint8_t v = ((num - 108) / 256) + 247; + const uint8_t w = (num - 108) % 256; + out_bytes->push_back(v); + out_bytes->push_back(w); + return true; + } + if (num <= -108 && num >= -1131) { + const uint8_t v = (-(num + 108) / 256) + 251; + const uint8_t w = -(num + 108) % 256; + out_bytes->push_back(v); + out_bytes->push_back(w); + return true; + } + if (num <= 32768 && num >= -32767) { + const uint8_t v = (num % 0xff00u) >> 8; + const uint8_t w = num % 0xffu; + out_bytes->push_back(28); + out_bytes->push_back(v); + out_bytes->push_back(w); + return true; + } + return false; +} + +// Adds a subroutine |subr| to |out_buffer| and |out_subr|. The contents of the +// subroutine is copied to |out_buffer|, and then the position of the subroutine +// in |out_buffer| is written to |out_subr|. Returns true on success. +bool AddSubr(const int *subr, size_t subr_len, + std::vector* out_buffer, ots::CFFIndex *out_subr) { + size_t pre_offset = out_buffer->size(); + for (size_t i = 0; i < subr_len; ++i) { + if (subr[i] != kOpPrefix) { + if (!EncodeNumber(subr[i], out_buffer)) { + return false; + } + } else { + if (i + 1 == subr_len) { + return false; + } + ++i; + if (!EncodeOperator(subr[i], out_buffer)) { + return false; + } + } + } + + ++(out_subr->count); + out_subr->off_size = 1; + if (out_subr->offsets.empty()) { + out_subr->offsets.push_back(pre_offset); + } + out_subr->offsets.push_back(out_buffer->size()); + return true; +} + +// Validates |char_string| and returns true if it's valid. +bool Validate(const int *char_string, size_t char_string_len, + const int *global_subrs, size_t global_subrs_len, + const int *local_subrs, size_t local_subrs_len) { + std::vector buffer; + ots::CFFIndex* char_strings_index = new ots::CFFIndex; + ots::CFFIndex global_subrs_index; + ots::CFFIndex* local_subrs_index = new ots::CFFIndex; + + if (char_string) { + if (!AddSubr(char_string, char_string_len, + &buffer, char_strings_index)) { + return false; + } + } + if (global_subrs) { + if (!AddSubr(global_subrs, global_subrs_len, + &buffer, &global_subrs_index)) { + return false; + } + } + if (local_subrs) { + if (!AddSubr(local_subrs, local_subrs_len, + &buffer, local_subrs_index)) { + return false; + } + } + + ots::Buffer ots_buffer(&buffer[0], buffer.size()); + + ots::FontFile* file = new ots::FontFile(); + file->context = new ots::OTSContext(); + ots::Font* font = new ots::Font(file); + ots::OpenTypeCFF* cff = new ots::OpenTypeCFF(font, OTS_TAG_CFF); + cff->charstrings_index = char_strings_index; + cff->local_subrs = local_subrs_index; + bool ret = ots::ValidateCFFCharStrings(*cff, + global_subrs_index, + &ots_buffer); + delete file->context; + delete file; + delete font; + delete cff; + + return ret; +} + +// Validates |char_string| and returns true if it's valid. +bool ValidateCharStrings(const int *char_string, size_t char_string_len) { + return Validate(char_string, char_string_len, NULL, 0, NULL, 0); +} + +} // namespace + +TEST(ValidateTest, TestRMoveTo) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kRMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, // width + 1, 2, kOpPrefix, ots::kRMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kRMoveTo, + 1, 2, 3, kOpPrefix, ots::kRMoveTo, // invalid number of args + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHMoveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kHMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, // width + 1, kOpPrefix, ots::kHMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kHMoveTo, + 1, 2, kOpPrefix, ots::kHMoveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVMoveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, // width + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, kOpPrefix, ots::kVMoveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRLineTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, kOpPrefix, ots::kRLineTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, kOpPrefix, ots::kRLineTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, kOpPrefix, ots::kRLineTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kRLineTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHLineTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kHLineTo, + 1, 2, kOpPrefix, ots::kHLineTo, + 1, 2, 3, kOpPrefix, ots::kHLineTo, + 1, 2, 3, 4, kOpPrefix, ots::kHLineTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHLineTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kHLineTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kHLineTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVLineTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kVLineTo, + 1, 2, kOpPrefix, ots::kVLineTo, + 1, 2, 3, kOpPrefix, ots::kVLineTo, + 1, 2, 3, 4, kOpPrefix, ots::kVLineTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVLineTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kVLineTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVLineTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRRCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, kOpPrefix, ots::kRRCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, kOpPrefix, ots::kRRCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kRRCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, kOpPrefix, ots::kRRCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHHCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, kOpPrefix, ots::kHHCurveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHHCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kHHCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, kOpPrefix, ots::kHHCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, kOpPrefix, ots::kHHCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHVCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + // The first form. + 1, 2, 3, 4, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + kOpPrefix, ots::kHVCurveTo, + // The second form. + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, kOpPrefix, ots::kHVCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kHVCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, kOpPrefix, ots::kHVCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kHVCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRCurveLine) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRCurveLine, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + kOpPrefix, ots::kRCurveLine, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kRCurveLine, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + // can't be the first op. + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRCurveLine, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRLineCurve) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRLineCurve, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, kOpPrefix, ots::kRLineCurve, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kRLineCurve, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + // can't be the first op. + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRLineCurve, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVHCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + // The first form. + 1, 2, 3, 4, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + kOpPrefix, ots::kVHCurveTo, + // The second form. + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, kOpPrefix, ots::kVHCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kVHCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, kOpPrefix, ots::kVHCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kVHCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVVCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, kOpPrefix, ots::kVVCurveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kVVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kVVCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kVVCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kVVCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kVVCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestFlex) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kFlex, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kFlex, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, kOpPrefix, ots::kFlex, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kFlex, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHFlex) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kHFlex, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kHFlex, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, kOpPrefix, ots::kHFlex, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kHFlex, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHFlex1) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHFlex1, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kHFlex1, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kHFlex1, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHFlex1, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestFlex1) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, kOpPrefix, ots::kFlex1, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kFlex1, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, kOpPrefix, ots::kFlex1, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, kOpPrefix, ots::kFlex1, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestEndChar) { + { + const int char_string[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } +} + +TEST(ValidateTest, TestHStem) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 0, // width + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 0, 1, 2, kOpPrefix, ots::kHStem, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHStem, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVStem) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kVStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kVStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 0, // width + 1, 2, kOpPrefix, ots::kVStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 0, 1, 2, kOpPrefix, ots::kVStem, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVStem, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHStemHm) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kHStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 0, // width + 1, 2, kOpPrefix, ots::kHStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 0, 1, 2, kOpPrefix, ots::kHStemHm, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHStemHm, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVStemHm) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kVStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kVStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 0, // width + 1, 2, kOpPrefix, ots::kVStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 0, 1, 2, kOpPrefix, ots::kVStemHm, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVStemHm, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHintMask) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kHintMask, 0x00, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + 3, 4, 5, 6, kOpPrefix, ots::kHintMask, 0x00, // vstem + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kHintMask, 0x00, // no stems to mask + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + 3, 4, 5, kOpPrefix, ots::kHintMask, 0x00, // invalid vstem + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestCntrMask) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kCntrMask, 0x00, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + 3, 4, 5, 6, kOpPrefix, ots::kCntrMask, 0x00, // vstem + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kCntrMask, 0x00, // no stems to mask + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + 3, 4, 5, kOpPrefix, ots::kCntrMask, 0x00, // invalid vstem + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestAbs) { + { + const int char_string[] = { + -1, kOpPrefix, ots::kAbs, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kAbs, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestAdd) { + { + const int char_string[] = { + 0, 1, kOpPrefix, ots::kAdd, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kAdd, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestSub) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kSub, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kSub, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestDiv) { + // TODO(yusukes): Test div-by-zero. + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kDiv, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kDiv, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestNeg) { + { + const int char_string[] = { + -1, kOpPrefix, ots::kNeg, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kNeg, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRandom) { + { + const int char_string[] = { + kOpPrefix, ots::kRandom, // OTS rejects the operator. + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestMul) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kMul, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kMul, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestSqrt) { + // TODO(yusukes): Test negative numbers. + { + const int char_string[] = { + 4, kOpPrefix, ots::kSqrt, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kSqrt, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestDrop) { + { + const int char_string[] = { + 1, 1, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kDrop, + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kDrop, // invalid + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestExch) { + { + const int char_string[] = { + 1, 1, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kDup, + kOpPrefix, ots::kExch, + kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 1, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kExch, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestIndex) { + { + const int char_string[] = { + 1, 2, 3, -1, kOpPrefix, ots::kIndex, // OTS rejects the operator. + kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRoll) { + { + const int char_string[] = { + 1, 2, 2, 1, kOpPrefix, ots::kRoll, // OTS rejects the operator. + kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestDup) { + { + const int char_string[] = { + 1, 1, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kDup, + kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kDup, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestPut) { + { + const int char_string[] = { + 1, 10, kOpPrefix, ots::kPut, // OTS rejects the operator. + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestGet) { + { + const int char_string[] = { + 1, 10, kOpPrefix, ots::kGet, // OTS rejects the operator. + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestAnd) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kAnd, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kAnd, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestOr) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kOr, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kOr, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestNot) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kNot, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kNot, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestEq) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kEq, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kEq, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestIfElse) { + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kIfElse, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, kOpPrefix, ots::kIfElse, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestCallSubr) { + // Call valid subr. + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + // Call undefined subr. + { + const int char_string[] = { + GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestCallGSubr) { + // Call valid subr. + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } + // Call undefined subr. + { + const int char_string[] = { + GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestCallGSubrWithComputedValues) { + { + // OTS does not allow to call(g)subr with a subroutine number which is + // not a immediate value for safety. + const int char_string[] = { + 0, 0, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } +} + +TEST(ValidateTest, TestInfiniteLoop) { + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } + // mutual recursion which doesn't stop. + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int global_subrs[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + local_subrs, ARRAYSIZE(local_subrs))); + } +} + +TEST(ValidateTest, TestStackOverflow) { + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, // overflow + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestDeprecatedOperators) { + { + const int char_string[] = { + kOpPrefix, 16, // 'blend'. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, (12 << 8) + 8, // 'store'. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, (12 << 8) + 13, // 'load'. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestUnterminatedCharString) { + // No endchar operator. + { + const int char_string[] = { + 123, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 123, 456, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 123, 456, kOpPrefix, ots::kReturn, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} diff --git a/gfx/ots/tests/layout_common_table_test.cc b/gfx/ots/tests/layout_common_table_test.cc new file mode 100644 index 000000000..91e429741 --- /dev/null +++ b/gfx/ots/tests/layout_common_table_test.cc @@ -0,0 +1,770 @@ +// Copyright (c) 2011-2017 The OTS 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 +#include +#include + +#include "layout.h" +#include "ots-memory-stream.h" + +namespace { + +const uint32_t kFakeTag = 0x00000000; +const size_t kScriptRecordSize = 6; +const size_t kLangSysRecordSize = 6; + +bool BuildFakeScriptListTable(ots::OTSStream *out, const uint16_t script_count, + const uint16_t langsys_count, + const uint16_t feature_count) { + if (!out->WriteU16(script_count)) { + return false; + } + const off_t script_record_end = out->Tell() + + kScriptRecordSize * script_count; + const size_t script_table_size = 4 + kLangSysRecordSize * langsys_count; + for (unsigned i = 0; i < script_count; ++i) { + if (!out->WriteU32(kFakeTag) || + !out->WriteU16(script_record_end + i * script_table_size)) { + return false; + } + } + + // Offsets to LangSys tables are measured from the beginning of each + // script table. + const off_t langsys_record_end = 4 + kLangSysRecordSize * langsys_count; + const size_t langsys_table_size = 6 + 2 * feature_count; + // Write Fake Script tables. + for (unsigned i = 0; i < script_count; ++i) { + if (!out->WriteU16(0x0000) || + !out->WriteU16(langsys_count)) { + return false; + } + for (unsigned j = 0; j < langsys_count; ++j) { + if (!out->WriteU32(kFakeTag) || + !out->WriteU16(langsys_record_end + j * langsys_table_size)) { + return false; + } + } + } + + // Write Fake LangSys tables. + for (unsigned i = 0; i < langsys_count; ++i) { + if (!out->WriteU16(0x0000) || + !out->WriteU16(0xFFFF) || + !out->WriteU16(feature_count)) { + return false; + } + for (unsigned j = 0; j < feature_count; ++j) { + if (!out->WriteU16(j)) { + return false; + } + } + } + return true; +} + +const size_t kFeatureRecordSize = 6; + +bool BuildFakeFeatureListTable(ots::OTSStream *out, + const uint16_t feature_count, + const uint16_t lookup_count) { + if (!out->WriteU16(feature_count)) { + return false; + } + const off_t feature_record_end = out->Tell() + + kFeatureRecordSize * feature_count; + const size_t feature_table_size = 4 + 2 * lookup_count; + for (unsigned i = 0; i < feature_count; ++i) { + if (!out->WriteU32(kFakeTag) || + !out->WriteU16(feature_record_end + i * feature_table_size)) { + return false; + } + } + + // Write FeatureTable + for (unsigned i = 0; i < feature_count; ++i) { + if (!out->WriteU16(0x0000) || + !out->WriteU16(lookup_count)) { + return false; + } + for (uint16_t j = 0; j < lookup_count; ++j) { + if (!out->WriteU16(j)) { + return false; + } + } + } + return true; +} + +bool BuildFakeLookupListTable(ots::OTSStream *out, const uint16_t lookup_count, + const uint16_t subtable_count) { + if (!out->WriteU16(lookup_count)) { + return false; + } + const off_t base_offset_lookup = out->Tell(); + if (!out->Pad(2 * lookup_count)) { + return false; + } + + std::vector offsets_lookup(lookup_count, 0); + for (uint16_t i = 0; i < lookup_count; ++i) { + offsets_lookup[i] = out->Tell(); + if (!out->WriteU16(i + 1) || + !out->WriteU16(0) || + !out->WriteU16(subtable_count) || + !out->Pad(2 * subtable_count) || + !out->WriteU16(0)) { + return false; + } + } + + const off_t offset_lookup_table_end = out->Tell(); + // Allocate 256 bytes for each subtable. + if (!out->Pad(256 * lookup_count * subtable_count)) { + return false; + } + + if (!out->Seek(base_offset_lookup)) { + return false; + } + for (unsigned i = 0; i < lookup_count; ++i) { + if (!out->WriteU16(offsets_lookup[i])) { + return false; + } + } + + for (unsigned i = 0; i < lookup_count; ++i) { + if (!out->Seek(offsets_lookup[i] + 6)) { + return false; + } + for (unsigned j = 0; j < subtable_count; ++j) { + if (!out->WriteU16(offset_lookup_table_end + + 256*i*subtable_count + 256*j)) { + return false; + } + } + } + return true; +} + +bool BuildFakeCoverageFormat1(ots::OTSStream *out, const uint16_t glyph_count) { + if (!out->WriteU16(1) || !out->WriteU16(glyph_count)) { + return false; + } + for (uint16_t glyph_id = 1; glyph_id <= glyph_count; ++glyph_id) { + if (!out->WriteU16(glyph_id)) { + return false; + } + } + return true; +} + +bool BuildFakeCoverageFormat2(ots::OTSStream *out, const uint16_t range_count) { + if (!out->WriteU16(2) || !out->WriteU16(range_count)) { + return false; + } + uint16_t glyph_id = 1; + uint16_t start_coverage_index = 0; + for (unsigned i = 0; i < range_count; ++i) { + // Write consecutive ranges in which each range consists of two glyph id. + if (!out->WriteU16(glyph_id) || + !out->WriteU16(glyph_id + 1) || + !out->WriteU16(start_coverage_index)) { + return false; + } + glyph_id += 2; + start_coverage_index += 2; + } + return true; +} + +bool BuildFakeClassDefFormat1(ots::OTSStream *out, const uint16_t glyph_count) { + if (!out->WriteU16(1) || + !out->WriteU16(1) || + !out->WriteU16(glyph_count)) { + return false; + } + for (uint16_t class_value = 1; class_value <= glyph_count; ++class_value) { + if (!out->WriteU16(class_value)) { + return false; + } + } + return true; +} + +bool BuildFakeClassDefFormat2(ots::OTSStream *out, const uint16_t range_count) { + if (!out->WriteU16(2) || !out->WriteU16(range_count)) { + return false; + } + uint16_t glyph_id = 1; + for (uint16_t class_value = 1; class_value <= range_count; ++class_value) { + // Write consecutive ranges in which each range consists of one glyph id. + if (!out->WriteU16(glyph_id) || + !out->WriteU16(glyph_id + 1) || + !out->WriteU16(class_value)) { + return false; + } + glyph_id += 2; + } + return true; +} + +bool BuildFakeDeviceTable(ots::OTSStream *out, const uint16_t start_size, + const uint16_t end_size, const uint16_t format) { + if (!out->WriteU16(start_size) || + !out->WriteU16(end_size) || + !out->WriteU16(format)) { + return false; + } + + const unsigned num_values = std::abs(end_size - start_size) + 1; + const unsigned num_bits = (1 << format) * num_values; + const unsigned num_units = (num_bits - 1) / 16 + 1; + if (!out->Pad(num_units * 2)) { + return false; + } + return true; +} + +class TestStream : public ots::MemoryStream { + public: + TestStream() + : ots::MemoryStream(data_, sizeof(data_)), size_(0) { + std::memset(reinterpret_cast(data_), 0, sizeof(data_)); + } + + uint8_t* data() { return data_; } + size_t size() const { return size_; } + + virtual bool WriteRaw(const void *d, size_t length) { + if (Tell() + length > size_) { + size_ = Tell() + length; + } + return ots::MemoryStream::WriteRaw(d, length); + } + + private: + size_t size_; + uint8_t data_[4096]; +}; + +class TableTest : public ::testing::Test { + protected: + + virtual void SetUp() { + ots::FontFile *file = new ots::FontFile(); + file->context = new ots::OTSContext(); + font = new ots::Font(file); + } + + virtual void TearDown() { + delete font->file->context; + delete font->file; + delete font; + } + + TestStream out; + ots::Font *font; +}; + +class ScriptListTableTest : public TableTest { }; +class DeviceTableTest : public TableTest { }; +class CoverageTableTest : public TableTest { }; +class CoverageFormat1Test : public TableTest { }; +class CoverageFormat2Test : public TableTest { }; +class ClassDefTableTest : public TableTest { }; +class ClassDefFormat1Test : public TableTest { }; +class ClassDefFormat2Test : public TableTest { }; +class LookupSubtableParserTest : public TableTest { }; + +class FeatureListTableTest : public TableTest { + protected: + + virtual void SetUp() { + TableTest::SetUp(); + num_features = 0; + } + + uint16_t num_features; +}; + +bool fakeTypeParserReturnsTrue(const ots::Font*, const uint8_t *, + const size_t) { + return true; +} + +bool fakeTypeParserReturnsFalse(const ots::Font*, const uint8_t *, + const size_t) { + return false; +} + +const ots::LookupSubtableParser::TypeParser TypeParsersReturnTrue[] = { + {1, fakeTypeParserReturnsTrue}, + {2, fakeTypeParserReturnsTrue}, + {3, fakeTypeParserReturnsTrue}, + {4, fakeTypeParserReturnsTrue}, + {5, fakeTypeParserReturnsTrue} +}; + +// Fake lookup subtable parser which always returns true. +const ots::LookupSubtableParser FakeLookupParserReturnsTrue = { + 5, 5, TypeParsersReturnTrue, +}; + +const ots::LookupSubtableParser::TypeParser TypeParsersReturnFalse[] = { + {1, fakeTypeParserReturnsFalse} +}; + +// Fake lookup subtable parser which always returns false. +const ots::LookupSubtableParser FakeLookupParserReturnsFalse = { + 1, 1, TypeParsersReturnFalse +}; + +class LookupListTableTest : public TableTest { + protected: + + virtual void SetUp() { + TableTest::SetUp(); + num_lookups = 0; + } + + bool Parse() { + return ots::ParseLookupListTable(font, out.data(), out.size(), + &FakeLookupParserReturnsTrue, + &num_lookups); + } + + uint16_t num_lookups; +}; + +} // namespace + +TEST_F(ScriptListTableTest, TestSuccess) { + BuildFakeScriptListTable(&out, 1, 1, 1); + EXPECT_TRUE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(ScriptListTableTest, TestBadScriptCount) { + BuildFakeScriptListTable(&out, 1, 1, 1); + // Set too large script count. + out.Seek(0); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(ScriptListTableTest, TestScriptRecordOffsetUnderflow) { + BuildFakeScriptListTable(&out, 1, 1, 1); + // Set bad offset to ScriptRecord[0]. + out.Seek(6); + out.WriteU16(0); + EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(ScriptListTableTest, TestScriptRecordOffsetOverflow) { + BuildFakeScriptListTable(&out, 1, 1, 1); + // Set bad offset to ScriptRecord[0]. + out.Seek(6); + out.WriteU16(out.size()); + EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(ScriptListTableTest, TestBadLangSysCount) { + BuildFakeScriptListTable(&out, 1, 1, 1); + // Set too large langsys count. + out.Seek(10); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(ScriptListTableTest, TestLangSysRecordOffsetUnderflow) { + BuildFakeScriptListTable(&out, 1, 1, 1); + // Set bad offset to LangSysRecord[0]. + out.Seek(16); + out.WriteU16(0); + EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(ScriptListTableTest, TestLangSysRecordOffsetOverflow) { + BuildFakeScriptListTable(&out, 1, 1, 1); + // Set bad offset to LangSysRecord[0]. + out.Seek(16); + out.WriteU16(out.size()); + EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(ScriptListTableTest, TestBadReqFeatureIndex) { + BuildFakeScriptListTable(&out, 1, 1, 1); + // Set too large feature index to ReqFeatureIndex of LangSysTable[0]. + out.Seek(20); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(ScriptListTableTest, TestBadFeatureCount) { + BuildFakeScriptListTable(&out, 1, 1, 1); + // Set too large feature count to LangSysTable[0]. + out.Seek(22); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(ScriptListTableTest, TestBadFeatureIndex) { + BuildFakeScriptListTable(&out, 1, 1, 1); + // Set too large feature index to ReatureIndex[0] of LangSysTable[0]. + out.Seek(24); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseScriptListTable(font, out.data(), out.size(), 1)); +} + +TEST_F(FeatureListTableTest, TestSuccess) { + BuildFakeFeatureListTable(&out, 1, 1); + EXPECT_TRUE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1, + &num_features)); + EXPECT_EQ(num_features, 1); +} + +TEST_F(FeatureListTableTest, TestSuccess2) { + BuildFakeFeatureListTable(&out, 5, 1); + EXPECT_TRUE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1, + &num_features)); + EXPECT_EQ(num_features, 5); +} + +TEST_F(FeatureListTableTest, TestBadFeatureCount) { + BuildFakeFeatureListTable(&out, 1, 1); + // Set too large feature count. + out.Seek(0); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1, + &num_features)); +} + +TEST_F(FeatureListTableTest, TestOffsetFeatureUnderflow) { + BuildFakeFeatureListTable(&out, 1, 1); + // Set bad offset to FeatureRecord[0]. + out.Seek(6); + out.WriteU16(0); + EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1, + &num_features)); +} + +TEST_F(FeatureListTableTest, TestOffsetFeatureOverflow) { + BuildFakeFeatureListTable(&out, 1, 1); + // Set bad offset to FeatureRecord[0]. + out.Seek(6); + out.WriteU16(out.size()); + EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1, + &num_features)); +} + +TEST_F(FeatureListTableTest, TestBadLookupCount) { + BuildFakeFeatureListTable(&out, 1, 1); + // Set too large lookup count to FeatureTable[0]. + out.Seek(10); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseFeatureListTable(font, out.data(), out.size(), 1, + &num_features)); +} + +TEST_F(LookupListTableTest, TestSuccess) { + BuildFakeLookupListTable(&out, 1, 1); + EXPECT_TRUE(Parse()); + EXPECT_EQ(num_lookups, 1); +} + +TEST_F(LookupListTableTest, TestSuccess2) { + BuildFakeLookupListTable(&out, 5, 1); + EXPECT_TRUE(Parse()); + EXPECT_EQ(num_lookups, 5); +} + +TEST_F(LookupListTableTest, TestOffsetLookupTableUnderflow) { + BuildFakeLookupListTable(&out, 1, 1); + // Set bad offset to Lookup[0]. + out.Seek(2); + out.WriteU16(0); + EXPECT_FALSE(Parse()); +} + +TEST_F(LookupListTableTest, TestOffsetLookupTableOverflow) { + BuildFakeLookupListTable(&out, 1, 1); + // Set bad offset to Lookup[0]. + out.Seek(2); + out.WriteU16(out.size()); + EXPECT_FALSE(Parse()); +} + +TEST_F(LookupListTableTest, TestOffsetSubtableUnderflow) { + BuildFakeLookupListTable(&out, 1, 1); + // Set bad offset to SubTable[0] of LookupTable[0]. + out.Seek(10); + out.WriteU16(0); + EXPECT_FALSE(Parse()); +} + +TEST_F(LookupListTableTest, TestOffsetSubtableOverflow) { + BuildFakeLookupListTable(&out, 1, 1); + // Set bad offset to SubTable[0] of LookupTable[0]. + out.Seek(10); + out.WriteU16(out.size()); + EXPECT_FALSE(Parse()); +} + +TEST_F(LookupListTableTest, TesBadLookupCount) { + BuildFakeLookupListTable(&out, 1, 1); + // Set too large lookup count of LookupTable[0]. + out.Seek(0); + out.WriteU16(2); + EXPECT_FALSE(Parse()); +} + +TEST_F(LookupListTableTest, TesBadLookupType) { + BuildFakeLookupListTable(&out, 1, 1); + // Set too large lookup type of LookupTable[0]. + out.Seek(4); + out.WriteU16(6); + EXPECT_FALSE(Parse()); +} + +TEST_F(LookupListTableTest, TesBadLookupFlag) { + BuildFakeLookupListTable(&out, 1, 1); + // Set IgnoreBaseGlyphs(0x0002) to the lookup flag of LookupTable[0]. + out.Seek(6); + out.WriteU16(0x0002); + EXPECT_TRUE(Parse()); +} + +TEST_F(LookupListTableTest, TesBadSubtableCount) { + BuildFakeLookupListTable(&out, 1, 1); + // Set too large sutable count of LookupTable[0]. + out.Seek(8); + out.WriteU16(2); + EXPECT_FALSE(Parse()); +} + +TEST_F(CoverageTableTest, TestSuccessFormat1) { + BuildFakeCoverageFormat1(&out, 1); + EXPECT_TRUE(ots::ParseCoverageTable(font, out.data(), out.size(), 1)); +} + +TEST_F(CoverageTableTest, TestSuccessFormat2) { + BuildFakeCoverageFormat2(&out, 1); + EXPECT_TRUE(ots::ParseCoverageTable(font, out.data(), out.size(), 1)); +} + +TEST_F(CoverageTableTest, TestBadFormat) { + BuildFakeCoverageFormat1(&out, 1); + // Set bad format. + out.Seek(0); + out.WriteU16(3); + EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1)); +} + +TEST_F(CoverageFormat1Test, TestBadGlyphCount) { + BuildFakeCoverageFormat1(&out, 1); + // Set too large glyph count. + out.Seek(2); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1)); +} + +TEST_F(CoverageFormat1Test, TestBadGlyphId) { + BuildFakeCoverageFormat1(&out, 1); + // Set too large glyph id. + out.Seek(4); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1)); +} + +TEST_F(CoverageFormat2Test, TestBadRangeCount) { + BuildFakeCoverageFormat2(&out, 1); + // Set too large range count. + out.Seek(2); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1)); +} + +TEST_F(CoverageFormat2Test, TestBadRange) { + BuildFakeCoverageFormat2(&out, 1); + // Set reverse order glyph id to start/end fields. + out.Seek(4); + out.WriteU16(2); + out.WriteU16(1); + EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 1)); +} + +TEST_F(CoverageFormat2Test, TestRangeOverlap) { + BuildFakeCoverageFormat2(&out, 2); + // Set overlapping glyph id to an end field. + out.Seek(12); + out.WriteU16(1); + EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 2)); +} + +TEST_F(CoverageFormat2Test, TestRangeOverlap2) { + BuildFakeCoverageFormat2(&out, 2); + // Set overlapping range. + out.Seek(10); + out.WriteU16(1); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseCoverageTable(font, out.data(), out.size(), 2)); +} + +TEST_F(ClassDefTableTest, TestSuccessFormat1) { + BuildFakeClassDefFormat1(&out, 1); + EXPECT_TRUE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1)); +} + +TEST_F(ClassDefTableTest, TestSuccessFormat2) { + BuildFakeClassDefFormat2(&out, 1); + EXPECT_TRUE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1)); +} + +TEST_F(ClassDefTableTest, TestBadFormat) { + BuildFakeClassDefFormat1(&out, 1); + // Set bad format. + out.Seek(0); + out.WriteU16(3); + EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1)); +} + +TEST_F(ClassDefFormat1Test, TestBadStartGlyph) { + BuildFakeClassDefFormat1(&out, 1); + // Set too large start glyph id. + out.Seek(2); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1)); +} + +TEST_F(ClassDefFormat1Test, TestBadGlyphCount) { + BuildFakeClassDefFormat1(&out, 1); + // Set too large glyph count. + out.Seek(4); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1)); +} + +TEST_F(ClassDefFormat1Test, TestBadClassValue) { + BuildFakeClassDefFormat1(&out, 1); + // Set too large class value. + out.Seek(6); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1)); +} + +TEST_F(ClassDefFormat2Test, TestBadRangeCount) { + BuildFakeClassDefFormat2(&out, 1); + // Set too large range count. + out.Seek(2); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1)); +} + +TEST_F(ClassDefFormat2Test, TestRangeOverlap) { + BuildFakeClassDefFormat2(&out, 2); + // Set overlapping glyph id to an end field. + out.Seek(12); + out.WriteU16(1); + EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1)); +} + +TEST_F(ClassDefFormat2Test, TestRangeOverlap2) { + BuildFakeClassDefFormat2(&out, 2); + // Set overlapping range. + out.Seek(10); + out.WriteU16(1); + out.WriteU16(2); + EXPECT_FALSE(ots::ParseClassDefTable(font, out.data(), out.size(), 1, 1)); +} + +TEST_F(DeviceTableTest, TestDeltaFormat1Success) { + BuildFakeDeviceTable(&out, 1, 8, 1); + EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size())); +} + +TEST_F(DeviceTableTest, TestDeltaFormat1Success2) { + BuildFakeDeviceTable(&out, 1, 9, 1); + EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size())); +} + +TEST_F(DeviceTableTest, TestDeltaFormat1Fail) { + // Pass shorter length than expected. + BuildFakeDeviceTable(&out, 1, 8, 1); + EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1)); +} + +TEST_F(DeviceTableTest, TestDeltaFormat1Fail2) { + // Pass shorter length than expected. + BuildFakeDeviceTable(&out, 1, 9, 1); + EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1)); +} + +TEST_F(DeviceTableTest, TestDeltaFormat2Success) { + BuildFakeDeviceTable(&out, 1, 1, 2); + EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size())); +} + +TEST_F(DeviceTableTest, TestDeltaFormat2Success2) { + BuildFakeDeviceTable(&out, 1, 8, 2); + EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size())); +} + +TEST_F(DeviceTableTest, TestDeltaFormat2Fail) { + // Pass shorter length than expected. + BuildFakeDeviceTable(&out, 1, 8, 2); + EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1)); +} + +TEST_F(DeviceTableTest, TestDeltaFormat2Fail2) { + // Pass shorter length than expected. + BuildFakeDeviceTable(&out, 1, 9, 2); + EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1)); +} + +TEST_F(DeviceTableTest, TestDeltaFormat3Success) { + BuildFakeDeviceTable(&out, 1, 1, 3); + EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size())); +} + +TEST_F(DeviceTableTest, TestDeltaFormat3Success2) { + BuildFakeDeviceTable(&out, 1, 8, 3); + EXPECT_TRUE(ots::ParseDeviceTable(font, out.data(), out.size())); +} + +TEST_F(DeviceTableTest, TestDeltaFormat3Fail) { + // Pass shorter length than expected. + BuildFakeDeviceTable(&out, 1, 8, 3); + EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1)); +} + +TEST_F(DeviceTableTest, TestDeltaFormat3Fail2) { + // Pass shorter length than expected. + BuildFakeDeviceTable(&out, 1, 9, 3); + EXPECT_FALSE(ots::ParseDeviceTable(font, out.data(), out.size() - 1)); +} + +TEST_F(LookupSubtableParserTest, TestSuccess) { + { + EXPECT_TRUE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 1)); + } + { + EXPECT_TRUE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 5)); + } +} + +TEST_F(LookupSubtableParserTest, TestFail) { + { + // Pass bad lookup type which less than the smallest type. + EXPECT_FALSE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 0)); + } + { + // Pass bad lookup type which greater than the maximum type. + EXPECT_FALSE(FakeLookupParserReturnsTrue.Parse(font, 0, 0, 6)); + } + { + // Check the type parser failure. + EXPECT_FALSE(FakeLookupParserReturnsFalse.Parse(font, 0, 0, 1)); + } +} -- cgit v1.2.3