diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/ots | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/ots')
106 files changed, 21989 insertions, 0 deletions
diff --git a/gfx/ots/LICENSE b/gfx/ots/LICENSE new file mode 100644 index 0000000000..d5e3bff5ad --- /dev/null +++ b/gfx/ots/LICENSE @@ -0,0 +1,27 @@ +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/RLBoxWOFF2Host.cpp b/gfx/ots/RLBoxWOFF2Host.cpp new file mode 100644 index 0000000000..172300f8dd --- /dev/null +++ b/gfx/ots/RLBoxWOFF2Host.cpp @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RLBoxWOFF2Host.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/RLBoxUtils.h" +#include "mozilla/ScopeExit.h" +#include "opentype-sanitiser.h" // For ots_ntohl + +using namespace rlbox; +using namespace mozilla; + +tainted_woff2<BrotliDecoderResult> RLBoxBrotliDecoderDecompressCallback( + rlbox_sandbox_woff2& aSandbox, tainted_woff2<unsigned long> aEncodedSize, + tainted_woff2<const char*> aEncodedBuffer, + tainted_woff2<unsigned long*> aDecodedSize, + tainted_woff2<char*> aDecodedBuffer) { + if (!aEncodedBuffer || !aDecodedSize || !aDecodedBuffer) { + return BROTLI_DECODER_RESULT_ERROR; + } + + // We don't create temporary buffers for brotli to operate on. Instead we + // pass a pointer to the in (encoded) and out (decoded) buffers. We check + // (specifically, unverified_safe_pointer checks) that the buffers are within + // the sandbox boundary (for the given sizes). + + size_t encodedSize = + aEncodedSize.unverified_safe_because("Any size within sandbox is ok."); + const uint8_t* encodedBuffer = reinterpret_cast<const uint8_t*>( + aEncodedBuffer.unverified_safe_pointer_because( + encodedSize, "Pointer fits within sandbox")); + + size_t decodedSize = + (*aDecodedSize).unverified_safe_because("Any size within sandbox is ok."); + uint8_t* decodedBuffer = + reinterpret_cast<uint8_t*>(aDecodedBuffer.unverified_safe_pointer_because( + decodedSize, "Pointer fits within sandbox")); + + BrotliDecoderResult res = BrotliDecoderDecompress( + encodedSize, encodedBuffer, &decodedSize, decodedBuffer); + + *aDecodedSize = decodedSize; + + return res; +} + +UniquePtr<RLBoxSandboxDataBase> RLBoxWOFF2SandboxPool::CreateSandboxData( + uint64_t aSize) { + // Create woff2 sandbox + auto sandbox = MakeUnique<rlbox_sandbox_woff2>(); + +#if defined(MOZ_WASM_SANDBOXING_WOFF2) + const w2c_mem_capacity capacity = + get_valid_wasm2c_memory_capacity(aSize, true /* 32-bit wasm memory*/); + bool createOK = sandbox->create_sandbox(/* infallible = */ false, &capacity); +#else + bool createOK = sandbox->create_sandbox(); +#endif + NS_ENSURE_TRUE(createOK, nullptr); + + UniquePtr<RLBoxWOFF2SandboxData> sbxData = + MakeUnique<RLBoxWOFF2SandboxData>(aSize, std::move(sandbox)); + + // Register brotli callback + sbxData->mDecompressCallback = sbxData->Sandbox()->register_callback( + RLBoxBrotliDecoderDecompressCallback); + sbxData->Sandbox()->invoke_sandbox_function(RegisterWOFF2Callback, + sbxData->mDecompressCallback); + + return sbxData; +} + +StaticRefPtr<RLBoxWOFF2SandboxPool> RLBoxWOFF2SandboxPool::sSingleton; + +void RLBoxWOFF2SandboxPool::Initalize(size_t aDelaySeconds) { + AssertIsOnMainThread(); + RLBoxWOFF2SandboxPool::sSingleton = new RLBoxWOFF2SandboxPool(aDelaySeconds); + ClearOnShutdown(&RLBoxWOFF2SandboxPool::sSingleton); +} + +RLBoxWOFF2SandboxData::RLBoxWOFF2SandboxData( + uint64_t aSize, mozilla::UniquePtr<rlbox_sandbox_woff2> aSandbox) + : mozilla::RLBoxSandboxDataBase(aSize), mSandbox(std::move(aSandbox)) { + MOZ_COUNT_CTOR(RLBoxWOFF2SandboxData); +} + +RLBoxWOFF2SandboxData::~RLBoxWOFF2SandboxData() { + MOZ_ASSERT(mSandbox); + mDecompressCallback.unregister(); + mSandbox->destroy_sandbox(); + MOZ_COUNT_DTOR(RLBoxWOFF2SandboxData); +} + +static bool Woff2SizeValidator(size_t aLength, size_t aSize, size_t aLimit) { + if (aSize < aLength) { + NS_WARNING("Size of decompressed WOFF 2.0 is less than compressed size"); + return false; + } else if (aSize == 0) { + NS_WARNING("Size of decompressed WOFF 2.0 is set to 0"); + return false; + } else if (aSize > aLimit) { + NS_WARNING( + nsPrintfCString("Size of decompressed WOFF 2.0 font exceeds %gMB", + aLimit / (1024.0 * 1024.0)) + .get()); + return false; + } + return true; +} + +// Code replicated from modules/woff2/src/woff2_dec.cc +// This is used both to compute the expected size of the Woff2 RLBox sandbox +// as well as internally by WOFF2 as a performance hint +static uint32_t ComputeWOFF2FinalSize(const uint8_t* aData, size_t aLength, + size_t aLimit) { + // Expected size is stored as a 4 byte value starting from the 17th byte + if (aLength < 20) { + return 0; + } + + uint32_t decompressedSize = 0; + const void* location = &(aData[16]); + std::memcpy(&decompressedSize, location, sizeof(decompressedSize)); + decompressedSize = ots_ntohl(decompressedSize); + + if (!Woff2SizeValidator(aLength, decompressedSize, aLimit)) { + return 0; + } + + return decompressedSize; +} + +template <typename T> +using TransferBufferToWOFF2 = + mozilla::RLBoxTransferBufferToSandbox<T, rlbox_woff2_sandbox_type>; +template <typename T> +using WOFF2Alloc = mozilla::RLBoxAllocateInSandbox<T, rlbox_woff2_sandbox_type>; + +bool RLBoxProcessWOFF2(ots::FontFile* aHeader, ots::OTSStream* aOutput, + const uint8_t* aData, size_t aLength, uint32_t aIndex, + ProcessTTCFunc* aProcessTTC, + ProcessTTFFunc* aProcessTTF) { + MOZ_ASSERT(aProcessTTC); + MOZ_ASSERT(aProcessTTF); + + // We index into aData before processing it (very end of this function). Our + // validator ensures that the untrusted size is greater than aLength, so we + // just need to conservatively ensure that aLength is greater than the highest + // index (7). + NS_ENSURE_TRUE(aLength >= 8, false); + + size_t limit = + std::min(size_t(OTS_MAX_DECOMPRESSED_FILE_SIZE), aOutput->size()); + uint32_t expectedSize = ComputeWOFF2FinalSize(aData, aLength, limit); + NS_ENSURE_TRUE(expectedSize > 0, false); + + // The sandbox should have space for the input, output and misc allocations + // To account for misc allocations, we'll set the sandbox size to: + // twice the size of (input + output) + + const uint64_t expectedSandboxSize = + static_cast<uint64_t>(2 * (aLength + expectedSize)); + auto sandboxPoolData = + RLBoxWOFF2SandboxPool::sSingleton->PopOrCreate(expectedSandboxSize); + NS_ENSURE_TRUE(sandboxPoolData, false); + + const auto* sandboxData = + static_cast<const RLBoxWOFF2SandboxData*>(sandboxPoolData->SandboxData()); + MOZ_ASSERT(sandboxData); + + auto* sandbox = sandboxData->Sandbox(); + + // Transfer aData into the sandbox. + + auto data = TransferBufferToWOFF2<char>( + sandbox, reinterpret_cast<const char*>(aData), aLength); + NS_ENSURE_TRUE(*data, false); + + // Perform the actual conversion to TTF. + + auto sizep = WOFF2Alloc<unsigned long>(sandbox); + auto bufp = WOFF2Alloc<char*>(sandbox); + auto bufOwnerString = + WOFF2Alloc<void*>(sandbox); // pointer to string that owns the bufer + + if (!sandbox + ->invoke_sandbox_function(RLBoxConvertWOFF2ToTTF, *data, aLength, + expectedSize, sizep.get(), + bufOwnerString.get(), bufp.get()) + .unverified_safe_because( + "The ProcessTT* functions validate the decompressed data.")) { + return false; + } + + auto bufCleanup = mozilla::MakeScopeExit([&sandbox, &bufOwnerString] { + // Delete the string created by RLBoxConvertWOFF2ToTTF. + sandbox->invoke_sandbox_function(RLBoxDeleteWOFF2String, + bufOwnerString.get()); + }); + + // Get the actual decompression size and validate it. + // We need to validate the size again. RLBoxConvertWOFF2ToTTF works even if + // the computed size (with ComputeWOFF2FinalSize) is wrong, so we can't + // trust the expectedSize to be the same as size sizep. + bool validateOK = false; + unsigned long actualSize = + (*sizep.get()).copy_and_verify([&](unsigned long val) { + validateOK = Woff2SizeValidator(aLength, val, limit); + return val; + }); + + NS_ENSURE_TRUE(validateOK, false); + + const uint8_t* decompressed = reinterpret_cast<const uint8_t*>( + (*bufp.get()) + .unverified_safe_pointer_because( + actualSize, + "Only care that the buffer is within sandbox boundary.")); + + // Since ProcessTT* memcpy from the buffer, make sure it's not null. + NS_ENSURE_TRUE(decompressed, false); + + if (aData[4] == 't' && aData[5] == 't' && aData[6] == 'c' && + aData[7] == 'f') { + return aProcessTTC(aHeader, aOutput, decompressed, actualSize, aIndex); + } + ots::Font font(aHeader); + return aProcessTTF(aHeader, &font, aOutput, decompressed, actualSize, 0); +} diff --git a/gfx/ots/RLBoxWOFF2Host.h b/gfx/ots/RLBoxWOFF2Host.h new file mode 100644 index 0000000000..9ed990c1cd --- /dev/null +++ b/gfx/ots/RLBoxWOFF2Host.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MODULES_WOFF2_RLBOXWOFF2_HOST_H_ +#define MODULES_WOFF2_RLBOXWOFF2_HOST_H_ + +#include "RLBoxWOFF2Types.h" + +// Load general firefox configuration of RLBox +#include "mozilla/rlbox/rlbox_config.h" + +#ifdef MOZ_WASM_SANDBOXING_WOFF2 +// Include the generated header file so that we are able to resolve the symbols +// in the wasm binary +# include "rlbox.wasm.h" +# define RLBOX_USE_STATIC_CALLS() rlbox_wasm2c_sandbox_lookup_symbol +# include "mozilla/rlbox/rlbox_wasm2c_sandbox.hpp" +#else +// Extra configuration for no-op sandbox +# define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol +# include "mozilla/rlbox/rlbox_noop_sandbox.hpp" +#endif + +#include "mozilla/rlbox/rlbox.hpp" + +#include "woff2/RLBoxWOFF2Sandbox.h" +#include "./src/ots.h" + +class RLBoxWOFF2SandboxData : public mozilla::RLBoxSandboxDataBase { + friend class RLBoxWOFF2SandboxPool; + + public: + RLBoxWOFF2SandboxData(uint64_t aSize, + mozilla::UniquePtr<rlbox_sandbox_woff2> aSandbox); + ~RLBoxWOFF2SandboxData(); + + rlbox_sandbox_woff2* Sandbox() const { return mSandbox.get(); } + + private: + mozilla::UniquePtr<rlbox_sandbox_woff2> mSandbox; + sandbox_callback_woff2<BrotliDecompressCallback*> mDecompressCallback; +}; + +using ProcessTTCFunc = bool(ots::FontFile* aHeader, ots::OTSStream* aOutput, + const uint8_t* aData, size_t aLength, + uint32_t aIndex); + +using ProcessTTFFunc = bool(ots::FontFile* aHeader, ots::Font* aFont, + ots::OTSStream* aOutput, const uint8_t* aData, + size_t aLength, uint32_t aOffset); + +bool RLBoxProcessWOFF2(ots::FontFile* aHeader, ots::OTSStream* aOutput, + const uint8_t* aData, size_t aLength, uint32_t aIndex, + ProcessTTCFunc* aProcessTTC, + ProcessTTFFunc* aProcessTTF); +#endif diff --git a/gfx/ots/RLBoxWOFF2Types.h b/gfx/ots/RLBoxWOFF2Types.h new file mode 100644 index 0000000000..aa291be85e --- /dev/null +++ b/gfx/ots/RLBoxWOFF2Types.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MODULES_WOFF2_RLBOXWOFF2TYPES_H_ +#define MODULES_WOFF2_RLBOXWOFF2TYPES_H_ + +#include <stddef.h> +#include "mozilla/rlbox/rlbox_types.hpp" +#include "woff2/decode.h" + +#ifdef MOZ_WASM_SANDBOXING_WOFF2 +RLBOX_DEFINE_BASE_TYPES_FOR(woff2, wasm2c) +#else +RLBOX_DEFINE_BASE_TYPES_FOR(woff2, noop) +#endif + +#include "mozilla/RLBoxSandboxPool.h" +#include "mozilla/StaticPtr.h" + +class RLBoxWOFF2SandboxPool : public mozilla::RLBoxSandboxPool { + public: + explicit RLBoxWOFF2SandboxPool(size_t aDelaySeconds) + : RLBoxSandboxPool(aDelaySeconds) {} + + static mozilla::StaticRefPtr<RLBoxWOFF2SandboxPool> sSingleton; + static void Initalize(size_t aDelaySeconds = 10); + + protected: + mozilla::UniquePtr<mozilla::RLBoxSandboxDataBase> CreateSandboxData( + uint64_t aSize) override; + ~RLBoxWOFF2SandboxPool() = default; +}; + +#endif diff --git a/gfx/ots/include/opentype-sanitiser.h b/gfx/ots/include/opentype-sanitiser.h new file mode 100644 index 0000000000..2625783a53 --- /dev/null +++ b/gfx/ots/include/opentype-sanitiser.h @@ -0,0 +1,228 @@ +// 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 OPENTYPE_SANITISER_H_ +#define OPENTYPE_SANITISER_H_ + +#if defined(_WIN32) || defined(__CYGWIN__) + #define OTS_DLL_IMPORT __declspec(dllimport) + #define OTS_DLL_EXPORT __declspec(dllexport) +#else + #if __GNUC__ >= 4 + #define OTS_DLL_IMPORT __attribute__((visibility ("default"))) + #define OTS_DLL_EXPORT __attribute__((visibility ("default"))) + #endif +#endif + +#ifdef OTS_DLL + #ifdef OTS_DLL_EXPORTS + #define OTS_API OTS_DLL_EXPORT + #else + #define OTS_API OTS_DLL_IMPORT + #endif +#else + #define OTS_API +#endif + +#if defined(_WIN32) +#include <stdlib.h> +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#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 <arpa/inet.h> +#include <stdint.h> +#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 <sys/types.h> + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstring> + +#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) ((char)((tag)>>24)), ((char)((tag)>>16)), ((char)((tag)>>8)), ((char)(tag)) + +#if defined(__GNUC__) && (__GNUC__ >= 4) || (__clang__) +#define OTS_UNUSED __attribute__((unused)) +#elif defined(_MSC_VER) +#define OTS_UNUSED __pragma(warning(suppress: 4100 4101)) +#else +#define OTS_UNUSED +#endif + +namespace ots { + +// ----------------------------------------------------------------------------- +// This is an interface for an abstract stream class which is used for writing +// the serialised results out. +// ----------------------------------------------------------------------------- +class OTSStream { + public: + OTSStream() : chksum_(0) {} + + virtual ~OTSStream() {} + + virtual size_t size() = 0; + + // This should be implemented to perform the actual write. + virtual bool WriteRaw(const void *data, size_t length) = 0; + + bool Write(const void *data, size_t length) { + if (!length) return false; + + const size_t orig_length = length; + size_t offset = 0; + + size_t chksum_offset = Tell() & 3; + if (chksum_offset) { + const size_t l = std::min(length, static_cast<size_t>(4) - chksum_offset); + uint32_t tmp = 0; + std::memcpy(reinterpret_cast<uint8_t *>(&tmp) + chksum_offset, data, l); + chksum_ += ots_ntohl(tmp); + length -= l; + offset += l; + } + + while (length >= 4) { + uint32_t tmp; + std::memcpy(&tmp, reinterpret_cast<const uint8_t *>(data) + offset, + sizeof(uint32_t)); + chksum_ += ots_ntohl(tmp); + length -= 4; + offset += 4; + } + + if (length) { + if (length > 4) return false; // not reached + uint32_t tmp = 0; + std::memcpy(&tmp, + reinterpret_cast<const uint8_t*>(data) + offset, length); + chksum_ += ots_ntohl(tmp); + } + + return WriteRaw(data, orig_length); + } + + virtual bool Seek(off_t position) = 0; + virtual off_t Tell() const = 0; + + virtual bool Pad(size_t bytes) { + static const uint32_t kZero = 0; + while (bytes >= 4) { + if (!Write(&kZero, 4)) return false; + bytes -= 4; + } + while (bytes) { + static const uint8_t kZerob = 0; + if (!Write(&kZerob, 1)) return false; + bytes--; + } + return true; + } + + bool WriteU8(uint8_t v) { + return Write(&v, sizeof(v)); + } + + bool WriteU16(uint16_t v) { + v = ots_htons(v); + return Write(&v, sizeof(v)); + } + + bool WriteS16(int16_t v) { + v = ots_htons(v); + return Write(&v, sizeof(v)); + } + + bool WriteU24(uint32_t v) { + v = ots_htonl(v); + return Write(reinterpret_cast<uint8_t*>(&v)+1, 3); + } + + bool WriteU32(uint32_t v) { + v = ots_htonl(v); + return Write(&v, sizeof(v)); + } + + bool WriteS32(int32_t v) { + v = ots_htonl(v); + return Write(&v, sizeof(v)); + } + + bool WriteR64(uint64_t v) { + return Write(&v, sizeof(v)); + } + + void ResetChecksum() { + assert((Tell() & 3) == 0); + chksum_ = 0; + } + + uint32_t chksum() const { + return chksum_; + } + + protected: + uint32_t chksum_; +}; + +#ifdef __GCC__ +#define MSGFUNC_FMT_ATTR __attribute__((format(printf, 2, 3))) +#else +#define MSGFUNC_FMT_ATTR +#endif + +enum TableAction { + TABLE_ACTION_DEFAULT, // Use OTS's default action for that table + TABLE_ACTION_SANITIZE, // Sanitize the table, potentially dropping it + TABLE_ACTION_PASSTHRU, // Serialize the table unchanged + TABLE_ACTION_DROP // Drop the table +}; + +class OTS_API OTSContext { + public: + OTSContext() {} + virtual ~OTSContext() {} + + // 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. + // input: the OpenType file + // length: the size, in bytes, of |input| + // index: if the input is a font collection and index is specified, then + // the corresponding font will be returned, otherwise the whole + // collection. Ignored for non-collection fonts. + bool Process(OTSStream *output, const uint8_t *input, size_t length, uint32_t index = -1); + + // This function will be called when OTS is reporting an error. + // level: the severity of the generated message: + // 0: error messages in case OTS fails to sanitize the font. + // 1: warning messages about issue OTS fixed in the sanitized font. + virtual void Message(int level OTS_UNUSED, const char *format OTS_UNUSED, ...) MSGFUNC_FMT_ATTR {} + + // This function will be called when OTS needs to decide what to do for a + // font table. + // tag: table tag formed with OTS_TAG() macro + virtual TableAction GetTableAction(uint32_t tag OTS_UNUSED) { return ots::TABLE_ACTION_DEFAULT; } +}; + +} // namespace ots + +#endif // OPENTYPE_SANITISER_H_ diff --git a/gfx/ots/include/ots-memory-stream.h b/gfx/ots/include/ots-memory-stream.h new file mode 100644 index 0000000000..cb844709c2 --- /dev/null +++ b/gfx/ots/include/ots-memory-stream.h @@ -0,0 +1,109 @@ +// 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_MEMORY_STREAM_H_ +#define OTS_MEMORY_STREAM_H_ + +#include <cstring> +#include <limits> + +#include "opentype-sanitiser.h" + +namespace ots { + +class MemoryStream : public OTSStream { + public: + MemoryStream(void *ptr, size_t length) + : ptr_(ptr), length_(length), off_(0) { + } + + size_t size() override { return length_; } + + bool WriteRaw(const void *data, size_t length) override { + if ((off_ + length > length_) || + (length > std::numeric_limits<size_t>::max() - off_)) { + return false; + } + std::memcpy(static_cast<char*>(ptr_) + off_, data, length); + off_ += length; + return true; + } + + bool Seek(off_t position) override { + if (position < 0) return false; + if (static_cast<size_t>(position) > length_) return false; + off_ = position; + return true; + } + + off_t Tell() const override { + return off_; + } + + private: + void* const ptr_; + size_t length_; + off_t off_; +}; + +class ExpandingMemoryStream : public OTSStream { + public: + ExpandingMemoryStream(size_t initial, size_t limit) + : length_(initial), limit_(limit), off_(0) { + ptr_ = new uint8_t[length_]; + } + + ~ExpandingMemoryStream() { + delete[] static_cast<uint8_t*>(ptr_); + } + + void* get() const { + return ptr_; + } + + size_t size() override { return limit_; } + + bool WriteRaw(const void *data, size_t length) override { + if ((off_ + length > length_) || + (length > std::numeric_limits<size_t>::max() - off_)) { + if (length_ == limit_) + return false; + size_t new_length = (length_ + 1) * 2; + if (new_length < length_) + return false; + if (new_length > limit_) + new_length = limit_; + uint8_t* new_buf = new uint8_t[new_length]; + std::memcpy(new_buf, ptr_, length_); + length_ = new_length; + delete[] static_cast<uint8_t*>(ptr_); + ptr_ = new_buf; + return WriteRaw(data, length); + } + std::memcpy(static_cast<char*>(ptr_) + off_, data, length); + off_ += length; + return true; + } + + bool Seek(off_t position) override { + if (position < 0) return false; + if (static_cast<size_t>(position) > length_) return false; + off_ = position; + return true; + } + + off_t Tell() const override { + return off_; + } + + private: + void* ptr_; + size_t length_; + const size_t limit_; + off_t off_; +}; + +} // namespace ots + +#endif // OTS_MEMORY_STREAM_H_ diff --git a/gfx/ots/moz.yaml b/gfx/ots/moz.yaml new file mode 100644 index 0000000000..50ce2b4e8f --- /dev/null +++ b/gfx/ots/moz.yaml @@ -0,0 +1,42 @@ +schema: 1 + +bugzilla: + product: Core + component: "Graphics: Text" + +origin: + name: ots + description: Sanitiser for OpenType project + + url: https://github.com/khaledhosny/ots + + release: 6ba665aa307ea360283191736814863ca398398d (2023-08-16T17:30:00Z). + revision: 6ba665aa307ea360283191736814863ca398398d + + license: BSD-3-Clause + license-file: LICENSE + +vendoring: + url: https://github.com/khaledhosny/ots + source-hosting: github + tracking: commit + + exclude: + - ".*" + - "**" + + include: + - include/ + - src/ + - tests/*.cc + + keep: + - LICENSE + - RLBoxWOFF2Host.* + - RLBoxWOFF2Types.* + + patches: + - ots-lz4.patch + - ots-rlbox.patch + - ots-visibility.patch + - ots-1850314.patch diff --git a/gfx/ots/ots-1850314.patch b/gfx/ots/ots-1850314.patch new file mode 100644 index 0000000000..88088f5064 --- /dev/null +++ b/gfx/ots/ots-1850314.patch @@ -0,0 +1,177 @@ +commit 362a59be47f9e187eec43df0938def661be6c972 +Author: Jonathan Kew <jkew@mozilla.com> +Date: Wed Aug 30 12:55:02 2023 +0000 + + Bug 1850314 - Don't do glyph bounding-box fixup for "tricky" fonts, because it may disrupt glyph rendering on macOS. r=gfx-reviewers,lsalzman + + Differential Revision: https://phabricator.services.mozilla.com/D187096 + +diff --git a/src/glyf.cc b/src/glyf.cc +index 0ed9515ef16d6..31487957bf99b 100644 +--- a/src/glyf.cc ++++ b/src/glyf.cc +@@ -10,6 +10,7 @@ + #include "head.h" + #include "loca.h" + #include "maxp.h" ++#include "name.h" + + // glyf - Glyph Data + // http://www.microsoft.com/typography/otspec/glyf.htm +@@ -97,7 +98,8 @@ bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph, + int16_t& xmin, + int16_t& ymin, + int16_t& xmax, +- int16_t& ymax) { ++ int16_t& ymax, ++ bool is_tricky_font) { + // read the end-points array + uint16_t num_flags = 0; + for (int i = 0; i < num_contours; ++i) { +@@ -219,27 +221,32 @@ bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph, + } + + if (adjusted_bbox) { +- Warning("Glyph bbox was incorrect; adjusting (glyph %u)", gid); +- // copy the numberOfContours field +- this->iov.push_back(std::make_pair(glyph.buffer(), 2)); +- // output a fixed-up version of the bounding box +- uint8_t* fixed_bbox = new uint8_t[8]; +- fixed_bboxes.push_back(fixed_bbox); +- xmin = ots_htons(xmin); +- std::memcpy(fixed_bbox, &xmin, 2); +- ymin = ots_htons(ymin); +- std::memcpy(fixed_bbox + 2, &ymin, 2); +- xmax = ots_htons(xmax); +- std::memcpy(fixed_bbox + 4, &xmax, 2); +- ymax = ots_htons(ymax); +- std::memcpy(fixed_bbox + 6, &ymax, 2); +- this->iov.push_back(std::make_pair(fixed_bbox, 8)); +- // copy the remainder of the glyph data +- this->iov.push_back(std::make_pair(glyph.buffer() + 10, glyph.offset() - 10)); +- } else { +- this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset())); ++ if (is_tricky_font) { ++ Warning("Glyph bbox was incorrect; NOT adjusting tricky font (glyph %u)", gid); ++ } else { ++ Warning("Glyph bbox was incorrect; adjusting (glyph %u)", gid); ++ // copy the numberOfContours field ++ this->iov.push_back(std::make_pair(glyph.buffer(), 2)); ++ // output a fixed-up version of the bounding box ++ uint8_t* fixed_bbox = new uint8_t[8]; ++ fixed_bboxes.push_back(fixed_bbox); ++ xmin = ots_htons(xmin); ++ std::memcpy(fixed_bbox, &xmin, 2); ++ ymin = ots_htons(ymin); ++ std::memcpy(fixed_bbox + 2, &ymin, 2); ++ xmax = ots_htons(xmax); ++ std::memcpy(fixed_bbox + 4, &xmax, 2); ++ ymax = ots_htons(ymax); ++ std::memcpy(fixed_bbox + 6, &ymax, 2); ++ this->iov.push_back(std::make_pair(fixed_bbox, 8)); ++ // copy the remainder of the glyph data ++ this->iov.push_back(std::make_pair(glyph.buffer() + 10, glyph.offset() - 10)); ++ return true; ++ } + } + ++ this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset())); ++ + return true; + } + +@@ -342,6 +349,10 @@ bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) { + return Error("Missing maxp or loca or head table needed by glyf table"); + } + ++ OpenTypeNAME *name = static_cast<OpenTypeNAME*>( ++ GetFont()->GetTypedTable(OTS_TAG_NAME)); ++ bool is_tricky = name->IsTrickyFont(); ++ + this->maxp = maxp; + + const unsigned num_glyphs = maxp->num_glyphs; +@@ -397,7 +408,7 @@ bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) { + // does we will simply ignore it. + glyph.set_offset(0); + } else if (num_contours > 0) { +- if (!ParseSimpleGlyph(glyph, i, num_contours, xmin, ymin, xmax, ymax)) { ++ if (!ParseSimpleGlyph(glyph, i, num_contours, xmin, ymin, xmax, ymax, is_tricky)) { + return Error("Failed to parse glyph %d", i); + } + } else { +diff --git a/src/glyf.h b/src/glyf.h +index 05e846f1cb6e8..f85fdc4652fcf 100644 +--- a/src/glyf.h ++++ b/src/glyf.h +@@ -51,7 +51,8 @@ class OpenTypeGLYF : public Table { + int16_t& xmin, + int16_t& ymin, + int16_t& xmax, +- int16_t& ymax); ++ int16_t& ymax, ++ bool is_tricky_font); + bool ParseCompositeGlyph( + Buffer &glyph, + ComponentPointCount* component_point_count); +diff --git a/src/name.cc b/src/name.cc +index fc5074b0587a3..7526e1f72b9ea 100644 +--- a/src/name.cc ++++ b/src/name.cc +@@ -366,4 +366,44 @@ bool OpenTypeNAME::IsValidNameId(uint16_t nameID, bool addIfMissing) { + return this->name_ids.count(nameID); + } + ++// List of font names considered "tricky" (dependent on applying original TrueType instructions) by FreeType, see ++// https://gitlab.freedesktop.org/freetype/freetype/-/blob/2d9fce53d4ce89f36075168282fcdd7289e082f9/src/truetype/ttobjs.c#L170-241 ++static const char* tricky_font_names[] = { ++ "cpop", ++ "DFGirl-W6-WIN-BF", ++ "DFGothic-EB", ++ "DFGyoSho-Lt", ++ "DFHei", ++ "DFHSGothic-W5", ++ "DFHSMincho-W3", ++ "DFHSMincho-W7", ++ "DFKaiSho-SB", ++ "DFKaiShu", ++ "DFKai-SB", ++ "DFMing", ++ "DLC", ++ "HuaTianKaiTi?", ++ "HuaTianSongTi?", ++ "Ming(for ISO10646)", ++ "MingLiU", ++ "MingMedium", ++ "PMingLiU", ++ "MingLi43" ++}; ++ ++bool OpenTypeNAME::IsTrickyFont() const { ++ for (const auto& name : this->names) { ++ const uint16_t id = name.name_id; ++ if (id != 1) { ++ continue; ++ } ++ for (const auto* p : tricky_font_names) { ++ if (name.text.find(p) != std::string::npos) { ++ return true; ++ } ++ } ++ } ++ return false; ++} ++ + } // namespace +diff --git a/src/name.h b/src/name.h +index 68c7ac096d3f8..a241e77ee26bb 100644 +--- a/src/name.h ++++ b/src/name.h +@@ -52,6 +52,7 @@ class OpenTypeNAME : public Table { + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + bool IsValidNameId(uint16_t nameID, bool addIfMissing = false); ++ bool IsTrickyFont() const; + + private: + std::vector<NameRecord> names; diff --git a/gfx/ots/ots-lz4.patch b/gfx/ots/ots-lz4.patch new file mode 100644 index 0000000000..9611396775 --- /dev/null +++ b/gfx/ots/ots-lz4.patch @@ -0,0 +1,74 @@ +diff --git a/src/glat.cc b/src/glat.cc +--- a/src/glat.cc ++++ b/src/glat.cc +@@ -4,9 +4,9 @@ + + #include "glat.h" + + #include "gloc.h" +-#include "lz4.h" ++#include "mozilla/Compression.h" + #include <list> + #include <memory> + + namespace ots { +@@ -214,16 +214,17 @@ bool OpenTypeGLAT_v3::Parse(const uint8_ + OTS_MAX_DECOMPRESSED_TABLE_SIZE / (1024.0 * 1024.0), + decompressed_size / (1024.0 * 1024.0)); + } + std::unique_ptr<uint8_t> decompressed(new uint8_t[decompressed_size]()); +- int ret = LZ4_decompress_safe_partial( ++ size_t outputSize = 0; ++ bool ret = mozilla::Compression::LZ4::decompressPartial( + reinterpret_cast<const char*>(data + table.offset()), +- reinterpret_cast<char*>(decompressed.get()), + table.remaining(), // input buffer size (input size + padding) ++ reinterpret_cast<char*>(decompressed.get()), + 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.get(), decompressed_size, true); + } + default: +diff --git a/src/silf.cc b/src/silf.cc +--- a/src/silf.cc ++++ b/src/silf.cc +@@ -4,9 +4,9 @@ + + #include "silf.h" + + #include "name.h" +-#include "lz4.h" ++#include "mozilla/Compression.h" + #include <cmath> + #include <memory> + + namespace ots { +@@ -49,16 +49,17 @@ bool OpenTypeSILF::Parse(const uint8_t* + OTS_MAX_DECOMPRESSED_TABLE_SIZE / (1024.0 * 1024.0), + decompressed_size / (1024.0 * 1024.0)); + } + std::unique_ptr<uint8_t> decompressed(new uint8_t[decompressed_size]()); +- int ret = LZ4_decompress_safe_partial( ++ size_t outputSize = 0; ++ bool ret = mozilla::Compression::LZ4::decompressPartial( + reinterpret_cast<const char*>(data + table.offset()), +- reinterpret_cast<char*>(decompressed.get()), + table.remaining(), // input buffer size (input size + padding) ++ reinterpret_cast<char*>(decompressed.get()), + 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.get(), decompressed_size, true); + } + default: diff --git a/gfx/ots/ots-rlbox.patch b/gfx/ots/ots-rlbox.patch new file mode 100644 index 0000000000..aa5763c91c --- /dev/null +++ b/gfx/ots/ots-rlbox.patch @@ -0,0 +1,60 @@ +diff --git a/src/ots.cc b/src/ots.cc +--- a/src/ots.cc ++++ b/src/ots.cc +@@ -14,7 +14,7 @@ + #include <map> + #include <vector> + +-#include <woff2/decode.h> ++#include "../RLBoxWOFF2Host.h" + + // The OpenType Font File + // http://www.microsoft.com/typography/otspec/otff.htm +@@ -511,43 +511,9 @@ bool ProcessWOFF(ots::FontFile *header, + return ProcessGeneric(header, font, woff_tag, output, data, length, tables, file); + } + +-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"); +- } +- // decompressed font must be <= OTS_MAX_DECOMPRESSED_FILE_SIZE +- if (decompressed_size > OTS_MAX_DECOMPRESSED_FILE_SIZE) { +- return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 font exceeds %gMB", +- OTS_MAX_DECOMPRESSED_FILE_SIZE / (1024.0 * 1024.0)); +- } +- +- if (decompressed_size > output->size()) { +- return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 font exceeds output size (%gMB)", output->size() / (1024.0 * 1024.0)); +- } +- +- std::string buf(decompressed_size, 0); +- woff2::WOFF2StringOut out(&buf); +- if (!woff2::ConvertWOFF2ToTTF(data, length, &out)) { +- return OTS_FAILURE_MSG_HDR("Failed to convert WOFF 2.0 font to SFNT"); +- } +- const uint8_t *decompressed = reinterpret_cast<const uint8_t*>(buf.data()); +- +- if (data[4] == 't' && data[5] == 't' && data[6] == 'c' && data[7] == 'f') { +- return ProcessTTC(header, output, decompressed, out.Size(), index); +- } else { +- ots::Font font(header); +- return ProcessTTF(header, &font, output, decompressed, out.Size()); +- } ++bool ProcessWOFF2(ots::FontFile* header, ots::OTSStream* output, ++ const uint8_t* data, size_t length, uint32_t index) { ++ return RLBoxProcessWOFF2(header, output, data, length, index, ProcessTTC, ProcessTTF); + } + + ots::TableAction GetTableAction(const ots::FontFile *header, uint32_t tag) { + diff --git a/gfx/ots/ots-visibility.patch b/gfx/ots/ots-visibility.patch new file mode 100644 index 0000000000..e9332de7d7 --- /dev/null +++ b/gfx/ots/ots-visibility.patch @@ -0,0 +1,43 @@ +diff --git a/include/opentype-sanitiser.h b/include/opentype-sanitiser.h +--- a/include/opentype-sanitiser.h ++++ b/include/opentype-sanitiser.h +@@ -4,8 +4,28 @@ + + #ifndef OPENTYPE_SANITISER_H_ + #define OPENTYPE_SANITISER_H_ + ++#if defined(_WIN32) || defined(__CYGWIN__) ++ #define OTS_DLL_IMPORT __declspec(dllimport) ++ #define OTS_DLL_EXPORT __declspec(dllexport) ++#else ++ #if __GNUC__ >= 4 ++ #define OTS_DLL_IMPORT __attribute__((visibility ("default"))) ++ #define OTS_DLL_EXPORT __attribute__((visibility ("default"))) ++ #endif ++#endif ++ ++#ifdef OTS_DLL ++ #ifdef OTS_DLL_EXPORTS ++ #define OTS_API OTS_DLL_EXPORT ++ #else ++ #define OTS_API OTS_DLL_IMPORT ++ #endif ++#else ++ #define OTS_API ++#endif ++ + #if defined(_WIN32) + #include <stdlib.h> + typedef signed char int8_t; + typedef unsigned char uint8_t; +@@ -164,9 +184,9 @@ enum TableAction { + TABLE_ACTION_PASSTHRU, // Serialize the table unchanged + TABLE_ACTION_DROP // Drop the table + }; + +-class OTSContext { ++class OTS_API OTSContext { + public: + OTSContext() {} + virtual ~OTSContext() {} + diff --git a/gfx/ots/src/avar.cc b/gfx/ots/src/avar.cc new file mode 100644 index 0000000000..62d806541f --- /dev/null +++ b/gfx/ots/src/avar.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 "avar.h" + +#include "fvar.h" + +#include "variations.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 > 2) { + return Drop("Unknown table version"); + } + if (this->majorVersion == 1) { + // We can fix table + 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; + } + } else { + // We serialize data unchanged, so drop even for minor errors + if (this->minorVersion > 0) { + return Drop("Unknown minor table version"); + } + if (this->reserved != 0) { + return Drop("Expected reserved=0"); + } + } + + OpenTypeFVAR* fvar = static_cast<OpenTypeFVAR*>( + 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"); + } + } + + if (this->majorVersion < 2) + return true; + + uint32_t axisIndexMapOffset; + uint32_t varStoreOffset; + + if (!table.ReadU32(&axisIndexMapOffset) || + !table.ReadU32(&varStoreOffset)) { + return Drop("Failed to read version 2 offsets"); + } + + Font *font = GetFont(); + uint32_t headerSize = table.offset(); + + if (axisIndexMapOffset) { + if (axisIndexMapOffset < headerSize || axisIndexMapOffset >= length) { + return Drop("Bad delta set index offset in table header"); + } + if (!ParseDeltaSetIndexMap(font, data + axisIndexMapOffset, length - axisIndexMapOffset)) { + return Drop("Failed to parse delta set index map"); + } + } + + if (varStoreOffset) { + if (varStoreOffset < headerSize || varStoreOffset >= length) { + return Drop("Bad item variation store offset in table header"); + } + if (!ParseItemVariationStore(font, data + varStoreOffset, length - varStoreOffset)) { + return Drop("Failed to parse item variation store"); + } + } + + this->m_data = data; + this->m_length = length; + + return true; +} + +bool OpenTypeAVAR::Serialize(OTSStream* out) { + if (this->majorVersion >= 2) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write table"); + } + return true; + } + + 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 0000000000..5954962cde --- /dev/null +++ b/gfx/ots/src/avar.h @@ -0,0 +1,46 @@ +// 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 <vector> + +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<std::vector<AxisValueMap>> axisSegmentMaps; + + // Only used for versions >= 2 + const uint8_t *m_data; + size_t m_length; +}; + +} // namespace ots + +#endif // OTS_AVAR_H_ diff --git a/gfx/ots/src/cff.cc b/gfx/ots/src/cff.cc new file mode 100644 index 0000000000..af54a4a16c --- /dev/null +++ b/gfx/ots/src/cff.cc @@ -0,0 +1,1381 @@ +// 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. + +#include "cff.h" + +#include <cstring> +#include <utility> +#include <vector> + +#include "maxp.h" +#include "cff_charstring.h" +#include "variations.h" + +// CFF - PostScript font program (Compact Font Format) table +// http://www.microsoft.com/typography/otspec/cff.htm +// http://www.microsoft.com/typography/otspec/cffspec.htm + +#define TABLE_NAME "CFF" + +namespace { + +enum DICT_OPERAND_TYPE { + DICT_OPERAND_INTEGER, + DICT_OPERAND_REAL, + DICT_OPERATOR, +}; + +enum DICT_DATA_TYPE { + DICT_DATA_TOPLEVEL, + DICT_DATA_FDARRAY, + DICT_DATA_PRIVATE, +}; + +enum FONT_FORMAT { + FORMAT_UNKNOWN, + FORMAT_CID_KEYED, + FORMAT_OTHER, // Including synthetic fonts +}; + +// see Appendix. A +const size_t kNStdString = 390; + +typedef std::pair<uint32_t, DICT_OPERAND_TYPE> Operand; + +bool ReadOffset(ots::Buffer &table, uint8_t off_size, uint32_t *offset) { + if (off_size > 4) { + return OTS_FAILURE(); + } + + uint32_t tmp32 = 0; + for (unsigned i = 0; i < off_size; ++i) { + uint8_t tmp8 = 0; + if (!table.ReadU8(&tmp8)) { + return OTS_FAILURE(); + } + tmp32 <<= 8; + tmp32 += tmp8; + } + *offset = tmp32; + return true; +} + +bool ParseIndex(ots::Buffer &table, ots::CFFIndex &index, bool cff2 = false) { + index.off_size = 0; + index.offsets.clear(); + + 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) { + // An empty INDEX. + index.offset_to_next = table.offset(); + return true; + } + + if (!table.ReadU8(&(index.off_size))) { + return OTS_FAILURE(); + } + if (index.off_size < 1 || index.off_size > 4) { + return OTS_FAILURE(); + } + + const size_t array_size = (index.count + 1) * index.off_size; + // less than ((64k + 1) * 4), thus does not overflow. + const size_t object_data_offset = table.offset() + array_size; + // does not overflow too, since offset() <= 1GB. + + if (object_data_offset >= table.length()) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i <= index.count; ++i) { // '<=' is not a typo. + uint32_t rel_offset = 0; + if (!ReadOffset(table, index.off_size, &rel_offset)) { + return OTS_FAILURE(); + } + if (rel_offset < 1) { + return OTS_FAILURE(); + } + if (i == 0 && rel_offset != 1) { + return OTS_FAILURE(); + } + + if (rel_offset > table.length()) { + return OTS_FAILURE(); + } + + // does not underflow. + if (object_data_offset > table.length() - (rel_offset - 1)) { + return OTS_FAILURE(); + } + + index.offsets.push_back( + object_data_offset + (rel_offset - 1)); // less than length(), 1GB. + } + + for (unsigned i = 1; i < index.offsets.size(); ++i) { + // We allow consecutive identical offsets here for zero-length strings. + // See http://crbug.com/69341 for more details. + if (index.offsets[i] < index.offsets[i - 1]) { + return OTS_FAILURE(); + } + } + + index.offset_to_next = index.offsets.back(); + return true; +} + +bool ParseNameData( + ots::Buffer *table, const ots::CFFIndex &index, std::string* out_name) { + uint8_t name[256] = {0}; + + 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 (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(); + } + // [, ], ... are not allowed. + if (std::strchr("[](){}<>/% ", name[i])) { + return OTS_FAILURE(); + } + } + + *out_name = reinterpret_cast<char *>(name); + return true; +} + +bool CheckOffset(const Operand& operand, size_t table_length) { + if (operand.second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operand.first >= table_length) { + return OTS_FAILURE(); + } + return true; +} + +bool CheckSid(const Operand& operand, size_t sid_max) { + if (operand.second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operand.first > sid_max) { + return OTS_FAILURE(); + } + return true; +} + +bool ParseDictDataBcd(ots::Buffer &table, std::vector<Operand> &operands) { + bool read_decimal_point = false; + bool read_e = false; + + uint8_t nibble = 0; + size_t count = 0; + while (true) { + if (!table.ReadU8(&nibble)) { + return OTS_FAILURE(); + } + if ((nibble & 0xf0) == 0xf0) { + if ((nibble & 0xf) == 0xf) { + // TODO(yusukes): would be better to store actual double value, + // rather than the dummy integer. + operands.push_back(std::make_pair(static_cast<uint32_t>(0), + DICT_OPERAND_REAL)); + return true; + } + return OTS_FAILURE(); + } + if ((nibble & 0x0f) == 0x0f) { + operands.push_back(std::make_pair(static_cast<uint32_t>(0), + DICT_OPERAND_REAL)); + return true; + } + + // check number format + uint8_t nibbles[2]; + nibbles[0] = (nibble & 0xf0) >> 8; + nibbles[1] = (nibble & 0x0f); + for (unsigned i = 0; i < 2; ++i) { + if (nibbles[i] == 0xd) { // reserved number + return OTS_FAILURE(); + } + if ((nibbles[i] == 0xe) && // minus + ((count > 0) || (i > 0))) { + return OTS_FAILURE(); // minus sign should be the first character. + } + if (nibbles[i] == 0xa) { // decimal point + if (!read_decimal_point) { + read_decimal_point = true; + } else { + return OTS_FAILURE(); // two or more points. + } + } + if ((nibbles[i] == 0xb) || // E+ + (nibbles[i] == 0xc)) { // E- + if (!read_e) { + read_e = true; + } else { + return OTS_FAILURE(); // two or more E's. + } + } + } + ++count; + } +} + +bool ParseDictDataEscapedOperator(ots::Buffer &table, + std::vector<Operand> &operands) { + uint8_t op = 0; + if (!table.ReadU8(&op)) { + return OTS_FAILURE(); + } + + if ((op <= 14) || + (op >= 17 && op <= 23) || + (op >= 30 && op <= 38)) { + operands.push_back(std::make_pair((12U << 8) + op, DICT_OPERATOR)); + return true; + } + + // reserved area. + return OTS_FAILURE(); +} + +bool ParseDictDataNumber(ots::Buffer &table, uint8_t b0, + std::vector<Operand> &operands) { + uint8_t b1 = 0; + uint8_t b2 = 0; + uint8_t b3 = 0; + uint8_t b4 = 0; + + switch (b0) { + case 28: // shortint + if (!table.ReadU8(&b1) || + !table.ReadU8(&b2)) { + return OTS_FAILURE(); + } + operands.push_back(std::make_pair( + static_cast<uint32_t>((b1 << 8) + b2), DICT_OPERAND_INTEGER)); + return true; + + case 29: // longint + if (!table.ReadU8(&b1) || + !table.ReadU8(&b2) || + !table.ReadU8(&b3) || + !table.ReadU8(&b4)) { + return OTS_FAILURE(); + } + operands.push_back(std::make_pair( + static_cast<uint32_t>((b1 << 24) + (b2 << 16) + (b3 << 8) + b4), + DICT_OPERAND_INTEGER)); + return true; + + case 30: // binary coded decimal + return ParseDictDataBcd(table, operands); + + default: + break; + } + + uint32_t result; + if (b0 >=32 && b0 <=246) { + result = b0 - 139; + } else if (b0 >=247 && b0 <= 250) { + if (!table.ReadU8(&b1)) { + return OTS_FAILURE(); + } + result = (b0 - 247) * 256 + b1 + 108; + } else if (b0 >= 251 && b0 <= 254) { + if (!table.ReadU8(&b1)) { + return OTS_FAILURE(); + } + result = -(b0 - 251) * 256 + b1 - 108; + } else { + return OTS_FAILURE(); + } + + operands.push_back(std::make_pair(result, DICT_OPERAND_INTEGER)); + return true; +} + +bool ParseDictDataReadNext(ots::Buffer &table, + std::vector<Operand> &operands) { + uint8_t op = 0; + if (!table.ReadU8(&op)) { + return OTS_FAILURE(); + } + if (op <= 24) { + if (op == 12) { + return ParseDictDataEscapedOperator(table, operands); + } + operands.push_back(std::make_pair( + static_cast<uint32_t>(op), DICT_OPERATOR)); + return true; + } else if (op <= 27 || op == 31 || op == 255) { + // reserved area. + return OTS_FAILURE(); + } + + return ParseDictDataNumber(table, op, operands); +} + +bool OperandsOverflow(std::vector<Operand>& 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<Operand>& 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( + ots::Buffer &table, size_t offset, size_t dict_length, + DICT_DATA_TYPE type, ots::OpenTypeCFF *out_cff) { + ots::Buffer dict(table.buffer() + offset, dict_length); + std::vector<Operand> operands; + bool cff2 = (out_cff->major == 2); + bool blend_seen = false; + int32_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 + // of FDArray the size of |local_subrs_per_font|. + // For CFF2, |vsindex_per_font| gets a similar treatment. + if (type == DICT_DATA_FDARRAY) { + out_cff->local_subrs_per_font.push_back(new ots::CFFIndex); + if (cff2) { + out_cff->vsindex_per_font.push_back(vsindex); + } + } + + while (dict.offset() < dict.length()) { + if (!ParseDictDataReadOperands(dict, operands, cff2)) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERATOR) { + continue; + } + + // got operator + 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.size() % 2) != 0) { + return OTS_FAILURE(); + } + break; + + // array + case (12U << 8) + 12: // StemSnapH (delta) + case (12U << 8) + 13: // StemSnapV (delta) + if (operands.empty()) { + return OTS_FAILURE(); + } + break; + + // number + case 10: // StdHW + case 11: // StdVW + case 20: // defaultWidthX + case 21: // nominalWidthX + case (12U << 8) + 9: // BlueScale + case (12U << 8) + 10: // BlueShift + case (12U << 8) + 11: // BlueFuzz + case (12U << 8) + 17: // LanguageGroup + case (12U << 8) + 18: // ExpansionFactor + case (12U << 8) + 19: // initialRandomSeed + if (operands.size() != 1) { + return OTS_FAILURE(); + } + break; + + // Local Subrs INDEX, offset(self) + case 19: { + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operands.back().first >= 1024 * 1024 * 1024) { + return OTS_FAILURE(); + } + if (operands.back().first + offset >= table.length()) { + return OTS_FAILURE(); + } + // parse "16. Local Subrs INDEX" + table.set_offset(operands.back().first + offset); + ots::CFFIndex *local_subrs_index = NULL; + if (type == DICT_DATA_FDARRAY) { + if (out_cff->local_subrs_per_font.empty()) { + return OTS_FAILURE(); // not reached. + } + local_subrs_index = out_cff->local_subrs_per_font.back(); + } else { // type == DICT_DATA_TOPLEVEL + if (out_cff->local_subrs) { + return OTS_FAILURE(); // two or more local_subrs? + } + local_subrs_index = new ots::CFFIndex; + out_cff->local_subrs = local_subrs_index; + } + if (!ParseIndex(table, *local_subrs_index, cff2)) { + return OTS_FAILURE(); + } + break; + } + + // boolean + case (12U << 8) + 14: // ForceBold + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operands.back().first >= 2) { + return OTS_FAILURE(); + } + break; + + 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 < 0 || + vsindex >= (int32_t)out_cff->region_index_count.size()) { + return OTS_FAILURE(); + } + out_cff->vsindex_per_font.back() = vsindex; + break; + } + + case 23: { // blend + if (!cff2) { + return OTS_FAILURE(); + } + if (operands.size() < 1) { + return OTS_FAILURE(); + } + if (vsindex >= (int32_t)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(); + } + 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(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) { + size_t dict_length = index.offsets[i] - index.offsets[i - 1]; + ots::Buffer dict(table.buffer() + index.offsets[i - 1], dict_length); + + if (!ParseDictData(table, dict, glyphs, sid_max, type, out_cff)) { + return OTS_FAILURE(); + } + } + return true; +} + +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<Operand> operands; + + FONT_FORMAT font_format = FORMAT_UNKNOWN; + bool have_ros = false; + bool have_charstrings = false; + bool have_vstore = false; + size_t charset_offset = 0; + bool have_private = false; + + 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; + + // got operator + const uint32_t op = operands.back().first; + operands.pop_back(); + + if (op == 18 && type == DICT_DATA_FDARRAY) { + have_private = true; + } + + 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); + + if (type == DICT_DATA_FDARRAY && !have_private) { + return OTS_FAILURE(); // CFF2 FD must have PrivateDICT entry (even if 0, 0) + } + + } + + while (dict.offset() < dict.length()) { + if (!ParseDictDataReadOperands(dict, operands, cff2)) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERATOR) continue; + + // got operator + const uint32_t op = operands.back().first; + operands.pop_back(); + + if (cff2 && !ValidCFF2DictOp(op, type)) { + 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; + + // array + case 5: // FontBBox + case 14: // XUID + case (12U << 8) + 7: // FontMatrix + case (12U << 8) + 23: // BaseFontBlend (delta) + if (operands.empty()) { + return OTS_FAILURE(); + } + break; + + // number + case 13: // UniqueID + case (12U << 8) + 2: // ItalicAngle + case (12U << 8) + 3: // UnderlinePosition + case (12U << 8) + 4: // UnderlineThickness + case (12U << 8) + 5: // PaintType + case (12U << 8) + 8: // StrokeWidth + case (12U << 8) + 20: // SyntheticBase + if (operands.size() != 1) { + return OTS_FAILURE(); + } + break; + case (12U << 8) + 31: // CIDFontVersion + case (12U << 8) + 32: // CIDFontRevision + case (12U << 8) + 33: // CIDFontType + case (12U << 8) + 34: // CIDCount + case (12U << 8) + 35: // UIDBase + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (font_format != FORMAT_CID_KEYED) { + return OTS_FAILURE(); + } + break; + case (12U << 8) + 6: // CharstringType + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if(operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operands.back().first != 2) { + // We only support the "Type 2 Charstring Format." + // TODO(yusukes): Support Type 1 format? Is that still in use? + return OTS_FAILURE(); + } + break; + + // boolean + case (12U << 8) + 1: // isFixedPitch + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operands.back().first >= 2) { + return OTS_FAILURE(); + } + break; + + // offset(0) + case 15: // charset + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().first <= 2) { + // predefined charset, ISOAdobe, Expert or ExpertSubset, is used. + break; + } + if (!CheckOffset(operands.back(), table.length())) { + return OTS_FAILURE(); + } + if (charset_offset) { + return OTS_FAILURE(); // multiple charset tables? + } + charset_offset = operands.back().first; + break; + + case 16: { // Encoding + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().first <= 1) { + break; // predefined encoding, "Standard" or "Expert", is used. + } + if (!CheckOffset(operands.back(), table.length())) { + return OTS_FAILURE(); + } + + 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; + } + } else if (format == 3) { + uint16_t n_ranges = 0; + if (!table.ReadU16(&n_ranges)) { + return OTS_FAILURE(); + } + if (n_ranges == 0) { + return OTS_FAILURE(); + } + + uint16_t last_gid = 0; + uint8_t fd_index = 0; + for (unsigned j = 0; j < n_ranges; ++j) { + uint16_t first = 0; // GID + if (!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. + } + 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.ReadU8(&fd_index)) { + return OTS_FAILURE(); + } + last_gid = first; + } + uint16_t sentinel = 0; + if (!table.ReadU16(&sentinel)) { + return OTS_FAILURE(); + } + if (last_gid >= sentinel) { + return OTS_FAILURE(); + } + if (sentinel > glyphs) { + return OTS_FAILURE(); // invalid gid. + } + for (auto k = last_gid; k < sentinel; ++k) { + if (!out_cff->fd_select.insert( + std::make_pair(k, fd_index)).second) { + return OTS_FAILURE(); + } + } + } else if (cff2 && format == 4) { + uint32_t n_ranges = 0; + if (!table.ReadU32(&n_ranges)) { + return OTS_FAILURE(); + } + if (n_ranges == 0) { + 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; + } + uint32_t sentinel = 0; + if (!table.ReadU32(&sentinel)) { + return OTS_FAILURE(); + } + if (last_gid >= sentinel) { + return OTS_FAILURE(); + } + if (sentinel > glyphs) { + return OTS_FAILURE(); // invalid gid. + } + for (auto k = last_gid; k < sentinel; ++k) { + if (!out_cff->fd_select.insert( + std::make_pair(k, fd_index)).second) { + return OTS_FAILURE(); + } + } + } else { + // unknown format + return OTS_FAILURE(); + } + break; + } + + // Private DICT (2 * number) + case 18: { + if (operands.size() != 2) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + const uint32_t private_offset = operands.back().first; + operands.pop_back(); + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + const uint32_t private_length = operands.back().first; + if (private_offset > table.length()) { + return OTS_FAILURE(); + } + if (private_length >= table.length()) { + return OTS_FAILURE(); + } + if (private_length + private_offset > table.length()) { + return OTS_FAILURE(); + } + // parse "15. Private DICT data" + if (!ParsePrivateDictData(table, private_offset, private_length, + type, out_cff)) { + return OTS_FAILURE(); + } + break; + } + + // ROS + case (12U << 8) + 30: + if (font_format != FORMAT_UNKNOWN) { + return OTS_FAILURE(); + } + font_format = FORMAT_CID_KEYED; + if (operands.size() != 3) { + return OTS_FAILURE(); + } + // check SIDs + operands.pop_back(); // ignore the first number. + if (!CheckSid(operands.back(), sid_max)) { + return OTS_FAILURE(); + } + operands.pop_back(); + if (!CheckSid(operands.back(), sid_max)) { + return OTS_FAILURE(); + } + if (have_ros) { + return OTS_FAILURE(); // multiple ROS tables? + } + have_ros = true; + break; + + default: + return OTS_FAILURE(); + } + operands.clear(); + + if (font_format == FORMAT_UNKNOWN) { + font_format = FORMAT_OTHER; + } + } + + // parse "13. Charsets" + if (charset_offset) { + 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(); + } + 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 (!table.ReadU16(&sid)) { + return OTS_FAILURE(); + } + if (!have_ros && (sid > sid_max)) { + return OTS_FAILURE(); + } + // TODO(yusukes): check CIDs when have_ros is true. + + if (format == 1) { + uint8_t left = 0; + if (!table.ReadU8(&left)) { + return OTS_FAILURE(); + } + total += (left + 1); + } else { + uint16_t left = 0; + if (!table.ReadU16(&left)) { + return OTS_FAILURE(); + } + total += (left + 1); + } + } + break; + } + + default: + return OTS_FAILURE(); + } + } + return true; +} + +} // namespace + +namespace ots { + +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 *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) || + !table.ReadU8(&minor) || + !table.ReadU8(&hdr_size) || + !table.ReadU8(&off_size)) { + return Error("Failed to read table header"); + } + + if (off_size < 1 || off_size > 4) { + return Error("Bad offSize: %d", off_size); + } + + if (major != 1 || minor != 0) { + return Error("Unsupported table version: %d.%d", major, minor); + } + + 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 Error("Failed to parse Name INDEX"); + } + 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 Error("Failed to parse Top DICT INDEX"); + } + 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 Error("Failed to parse String INDEX"); + } + if (string_index.count >= 65000 - kNStdString) { + return Error("Too many entries in String INDEX: %d", string_index.count); + } + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + 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" + this->charstrings_index = new ots::CFFIndex; + if (!ParseDictData(table, top_dict_index, + num_glyphs, sid_max, + 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 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; +} + +bool OpenTypeCFF::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write table"); + } + return true; +} + +OpenTypeCFF::~OpenTypeCFF() { + for (size_t i = 0; i < this->local_subrs_per_font.size(); ++i) { + delete (this->local_subrs_per_font)[i]; + } + delete this->charstrings_index; + delete this->local_subrs; +} + +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<OpenTypeMAXP*>( + 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; +} + +bool OpenTypeCFF2::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write table"); + } + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/cff.h b/gfx/ots/src/cff.h new file mode 100644 index 0000000000..03e7bb348f --- /dev/null +++ b/gfx/ots/src/cff.h @@ -0,0 +1,98 @@ +// 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_CFF_H_ +#define OTS_CFF_H_ + +#include "ots.h" + +#include <map> +#include <string> +#include <vector> + +#undef major // glibc defines major! + +namespace ots { + +struct CFFIndex { + CFFIndex() + : count(0), off_size(0), offset_to_next(0) {} + uint32_t count; + uint8_t off_size; + std::vector<uint32_t> offsets; + uint32_t offset_to_next; +}; + +typedef std::map<uint32_t, uint16_t> 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 #. + CFFFDSelect fd_select; + + // A list of char strings. + CFFIndex* charstrings_index; + // A list of Local Subrs associated with FDArrays. Can be empty. + std::vector<CFFIndex *> local_subrs_per_font; + // A Local Subrs associated with Top DICT. Can be NULL. + CFFIndex *local_subrs; + + // CFF2 VariationStore regionIndexCount. + std::vector<uint16_t> region_index_count; + + // CFF2 vsindex: per FontDICT->PrivateDICT + // default of 0 is stored for each font if not + // explicitly set in Font's PrivateDICT. + std::vector<int32_t> vsindex_per_font; + + 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 + +#endif // OTS_CFF_H_ diff --git a/gfx/ots/src/cff_charstring.cc b/gfx/ots/src/cff_charstring.cc new file mode 100644 index 0000000000..9ea5d407e2 --- /dev/null +++ b/gfx/ots/src/cff_charstring.cc @@ -0,0 +1,1020 @@ +// 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 <climits> +#include <cstdio> +#include <cstring> +#include <stack> +#include <string> +#include <utility> + +#define TABLE_NAME "CFF" + +namespace { + +// Type 2 Charstring Implementation Limits. See Appendix. B in Adobe Technical +// Note #5177. +const int32_t kMaxSubrsCount = 65536; +const size_t kMaxCharStringLength = 65535; +const size_t 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<int32_t> *argument_stack, + ots::CharStringContext& cs_ctx); + +bool ArgumentStackOverflows(std::stack<int32_t> *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<int32_t>(v) - 139; + } else if (v <= 250) { + uint8_t w = 0; + if (!char_string->ReadU8(&w)) { + return OTS_FAILURE(); + } + *out_number = ((static_cast<int32_t>(v) - 247) * 256) + + static_cast<int32_t>(w) + 108; + } else if (v <= 254) { + uint8_t w = 0; + if (!char_string->ReadU8(&w)) { + return OTS_FAILURE(); + } + *out_number = -((static_cast<int32_t>(v) - 251) * 256) - + static_cast<int32_t>(w) - 108; + } else if (v == 255) { + // TODO(yusukes): We should not skip the 4 bytes. Note that when v is 255, + // we should treat the following 4-bytes as a 16.16 fixed-point number + // rather than 32bit signed int. + if (!char_string->Skip(4)) { + return OTS_FAILURE(); + } + *out_number = dummy_result; + } else { + return OTS_FAILURE(); + } + + return true; +} + +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 |cs_ctx| argument holds values that +// need to persist through these calls (see CharStringContext for details) +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<int32_t> *argument_stack, + ots::CharStringContext& cs_ctx) { + ots::Font* font = cff.GetFont(); + const size_t stack_size = argument_stack->size(); + + if (cs_ctx.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<size_t>(subr_number + 1)) { + return OTS_FAILURE(); // The number is out-of-bounds. + } + + // Prepare ots::Buffer where we're going to jump. + const size_t length = + subrs_index.offsets[subr_number + 1] - subrs_index.offsets[subr_number]; + if (length > kMaxCharStringLength) { + return OTS_FAILURE(); + } + const size_t offset = subrs_index.offsets[subr_number]; + cff_table->set_offset(offset); + if (!cff_table->Skip(length)) { + return OTS_FAILURE(); + } + ots::Buffer char_string_to_jump(cff_table->buffer() + offset, length); + + return ExecuteCharString(cff, + call_depth + 1, + global_subrs_index, + local_subrs_index, + cff_table, + &char_string_to_jump, + argument_stack, + cs_ctx); + } + + case ots::kReturn: + return true; + + case ots::kEndChar: + cs_ctx.endchar_seen = true; + cs_ctx.width_seen = true; // just in case. + return true; + + case ots::kVSIndex: { + if (!cs_ctx.cff2) { + return OTS_FAILURE(); + } + if (stack_size != 1) { + return OTS_FAILURE(); + } + if (cs_ctx.blend_seen || cs_ctx.vsindex_seen) { + return OTS_FAILURE(); + } + if (argument_stack->top() < 0 || + argument_stack->top() >= (int32_t)cff.region_index_count.size()) { + return OTS_FAILURE(); + } + cs_ctx.vsindex_seen = true; + cs_ctx.vsindex = argument_stack->top(); + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + } + + case ots::kBlend: { + if (!cs_ctx.cff2) { + return OTS_FAILURE(); + } + if (stack_size < 1) { + return OTS_FAILURE(); + } + if (cs_ctx.vsindex >= (int32_t)cff.region_index_count.size()) { + return OTS_FAILURE(); + } + uint16_t k = cff.region_index_count.at(cs_ctx.vsindex); + uint16_t n = argument_stack->top(); + if (stack_size < n * (k + 1u) + 1u) { + 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 operators, 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(); + cs_ctx.blend_seen = 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 ((!(cs_ctx.width_seen)) && (((stack_size - 1) % 2) == 0)) { + // The -1 is for "width" argument. For details, see Adobe Technical Note + // #5177, page 16, note 4. + successful = true; + } + cs_ctx.num_stems += (stack_size / 2); + if ((cs_ctx.num_stems) > kMaxNumberOfStemHints) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + cs_ctx.width_seen = 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 ((!(cs_ctx.width_seen)) && (stack_size - 1 == 2)) { + successful = true; + } + while (!argument_stack->empty()) + argument_stack->pop(); + cs_ctx.width_seen = true; + return successful ? true : OTS_FAILURE(); + } + + case ots::kVMoveTo: + case ots::kHMoveTo: { + bool successful = false; + if (stack_size == 1) { + successful = true; + } else if ((!(cs_ctx.width_seen)) && (stack_size - 1 == 1)) { + successful = true; + } + while (!argument_stack->empty()) + argument_stack->pop(); + cs_ctx.width_seen = true; + return successful ? true : OTS_FAILURE(); + } + + case ots::kHintMask: + case ots::kCntrMask: { + bool successful = false; + if (stack_size == 0) { + successful = true; + } else if ((!(cs_ctx.width_seen)) && (stack_size == 1)) { + // A number for "width" is found. + successful = true; + } else if ((!(cs_ctx.width_seen)) || // in this case, any sizes are ok. + ((stack_size % 2) == 0)) { + // The numbers are vstem definition. + // See Adobe Technical Note #5177, page 24, hintmask. + cs_ctx.num_stems += (stack_size / 2); + if ((cs_ctx.num_stems) > kMaxNumberOfStemHints) { + return OTS_FAILURE(); + } + successful = true; + } + if (!successful) { + return OTS_FAILURE(); + } + + if ((cs_ctx.num_stems) == 0) { + return OTS_FAILURE(); + } + const size_t mask_bytes = (cs_ctx.num_stems + 7) / 8; + if (!char_string->Skip(mask_bytes)) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + cs_ctx.width_seen = true; + return true; + } + + case ots::kRLineTo: + if (!(cs_ctx.width_seen)) { + // 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 (!(cs_ctx.width_seen)) { + return OTS_FAILURE(); + } + if (stack_size < 1) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kRRCurveTo: + if (!(cs_ctx.width_seen)) { + 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 (!(cs_ctx.width_seen)) { + 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 (!(cs_ctx.width_seen)) { + 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 (!(cs_ctx.width_seen)) { + 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 (!(cs_ctx.width_seen)) { + 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 (!(cs_ctx.width_seen)) { + 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, cs_ctx.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 (!(cs_ctx.width_seen)) { + return OTS_FAILURE(); + } + if (stack_size != 7) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kFlex: + if (!(cs_ctx.width_seen)) { + return OTS_FAILURE(); + } + if (stack_size != 13) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kHFlex1: + if (!(cs_ctx.width_seen)) { + return OTS_FAILURE(); + } + if (stack_size != 9) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kFlex1: + if (!(cs_ctx.width_seen)) { + 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|. +// +// cff: parent OpenTypeCFF reference +// 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. +// cs_ctx: a CharStringContext holding values to persist across subrs, etc. +// endchar_seen: true is set if |char_string| contains 'endchar'. +// width_seen: true is set if |char_string| contains 'width' byte (which +// is 0 or 1 byte) or if cff2 +// num_stems: total number of hstems and vstems processed so far. +// cff2: true if this is a CFF2 table +// blend_seen: initially false; set to true if 'blend' operator encountered. +// vsindex_seen: initially false; set to true if 'vsindex' encountered. +// vsindex: initially = PrivateDICT's vsindex; may be changed by 'vsindex' +// operator in CharString +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<int32_t> *argument_stack, + ots::CharStringContext& cs_ctx) { + if (call_depth > kMaxSubrNesting) { + return OTS_FAILURE(); + } + cs_ctx.endchar_seen = false; + + 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, cs_ctx.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, + cs_ctx)) { + return OTS_FAILURE(); + } + if (cs_ctx.endchar_seen) { + return true; + } + if (operator_or_operand == ots::kReturn) { + return true; + } + } + + // No endchar operator is found (CFF1 only) + if (cs_ctx.cff2) + return true; + return OTS_FAILURE(); +} + +// Selects a set of subroutines 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. + } + + // 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<int32_t> argument_stack; + // Context to store values that must persist across subrs, etc. + CharStringContext cs_ctx; + cs_ctx.cff2 = (cff.major == 2); + // CFF2 CharString has no value for width, so we start with true here to + // error out if width is found. + cs_ctx.width_seen = cs_ctx.cff2; + // CFF2 CharStrings' default vsindex comes from the associated PrivateDICT + if (cs_ctx.cff2) { + const auto& iter = cff.fd_select.find(glyph_index); + auto fd_index = 0; + if (iter != cff.fd_select.end()) { + fd_index = iter->second; + } + if (fd_index >= (int32_t)cff.vsindex_per_font.size()) { + // shouldn't get this far with a font in this condition, but just in case + return OTS_FAILURE(); // fd_index out-of-range + } + cs_ctx.vsindex = cff.vsindex_per_font.at(fd_index); + } + +#ifdef DUMP_T2CHARSTRING + fprintf(stderr, "\n---- CharString %*d ----\n", 5, glyph_index); +#endif + + if (!ExecuteCharString(cff, + 0 /* initial call_depth is zero */, + global_subrs_index, *local_subrs_to_use, + cff_table, &char_string, &argument_stack, + cs_ctx)) { + return OTS_FAILURE(); + } + if (!cs_ctx.cff2 && !cs_ctx.endchar_seen) { + 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 0000000000..b407db564f --- /dev/null +++ b/gfx/ots/src/cff_charstring.h @@ -0,0 +1,110 @@ +// 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 <map> +#include <vector> + +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 the max argument stack size (48 for CFF1; 513 for CFF2) +// 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: +// cff: parent OpenTypeCFF reference +// global_subrs_index: Global subroutines which could be called by a charstring +// in |char_strings_index|. +// 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 will be rejected. +}; + +struct CharStringContext { + bool endchar_seen = false; + bool width_seen = false; + size_t num_stems = 0; + bool cff2 = false; + bool blend_seen = false; + bool vsindex_seen = false; + int32_t vsindex = 0; +}; + +} // namespace ots + +#endif // OTS_CFF_TYPE2_CHARSTRING_H_ diff --git a/gfx/ots/src/cmap.cc b/gfx/ots/src/cmap.cc new file mode 100644 index 0000000000..5673c766d3 --- /dev/null +++ b/gfx/ots/src/cmap.cc @@ -0,0 +1,1076 @@ +// 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 "cmap.h" + +#include <algorithm> +#include <set> +#include <utility> +#include <vector> + +#include "maxp.h" +#include "os2.h" + +// cmap - Character To Glyph Index Mapping Table +// http://www.microsoft.com/typography/otspec/cmap.htm + +namespace { + +struct CMAPSubtableHeader { + uint16_t platform; + uint16_t encoding; + uint32_t offset; + uint16_t format; + uint32_t length; + uint32_t language; +}; + +struct Subtable314Range { + uint16_t start_range; + uint16_t end_range; + int16_t id_delta; + uint16_t id_range_offset; + uint32_t id_range_offset_offset; +}; + +// Glyph array size for the Mac Roman (format 0) table. +const size_t kFormat0ArraySize = 256; + +// The upper limit of the Unicode code point. +const uint32_t kUnicodeUpperLimit = 0x10FFFF; + +// The maximum number of UVS records (See below). +const uint32_t kMaxCMAPSelectorRecords = 259; +// The range of UVSes are: +// 0x180B-0x180D (3 code points) +// 0xFE00-0xFE0F (16 code points) +// 0xE0100-0xE01EF (240 code points) +const uint32_t kMongolianVSStart = 0x180B; +const uint32_t kMongolianVSEnd = 0x180D; +const uint32_t kVSStart = 0xFE00; +const uint32_t kVSEnd = 0xFE0F; +const uint32_t kIVSStart = 0xE0100; +const uint32_t kIVSEnd = 0xE01EF; +const uint32_t kUVSUpperLimit = 0xFFFFFF; + +} // namespace + +namespace ots { + +// Parses Format 4 tables +bool OpenTypeCMAP::ParseFormat4(int platform, int encoding, + const uint8_t *data, size_t length, uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // 0.3.4, 3.0.4 or 3.1.4 subtables are complex and, rather than expanding the + // whole thing and recompacting it, we validate it and include it verbatim + // in the output. + + OpenTypeOS2 *os2 = static_cast<OpenTypeOS2*>( + GetFont()->GetTypedTable(OTS_TAG_OS2)); + if (!os2) { + return Error("Required OS/2 table missing"); + } + + if (!subtable.Skip(4)) { + return Error("Can't read 4 bytes at start of cmap format 4 subtable"); + } + uint16_t language = 0; + if (!subtable.ReadU16(&language)) { + return Error("Can't read language"); + } + if (language) { + // Platform ID 3 (windows) subtables should have language '0'. + return Error("Languages should be 0 (%d)", language); + } + + uint16_t segcountx2, search_range, entry_selector, range_shift; + segcountx2 = search_range = entry_selector = range_shift = 0; + if (!subtable.ReadU16(&segcountx2) || + !subtable.ReadU16(&search_range) || + !subtable.ReadU16(&entry_selector) || + !subtable.ReadU16(&range_shift)) { + return Error("Failed to read subcmap structure"); + } + + if (segcountx2 & 1 || search_range & 1) { + 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 Error("Segcount < 1 (%d)", segcount); + } + + // log2segcount is the maximal x s.t. 2^x < segcount + unsigned log2segcount = 0; + while (1u << (log2segcount + 1) <= segcount) { + log2segcount++; + } + + const uint16_t expected_search_range = 2 * 1u << log2segcount; + if (expected_search_range != search_range) { + return Error("expected search range != search range (%d != %d)", expected_search_range, search_range); + } + + if (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 Error("unexpected range shift (%d != %d)", range_shift, expected_range_shift); + } + + std::vector<Subtable314Range> ranges(segcount); + + for (unsigned i = 0; i < segcount; ++i) { + if (!subtable.ReadU16(&ranges[i].end_range)) { + return Error("Failed to read segment %d", i); + } + } + + uint16_t padding; + if (!subtable.ReadU16(&padding)) { + return Error("Failed to read cmap subtable segment padding"); + } + if (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 Error("Failed to read segment start range %d", i); + } + } + for (unsigned i = 0; i < segcount; ++i) { + if (!subtable.ReadS16(&ranges[i].id_delta)) { + 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 Error("Failed to read segment range offset %d", i); + } + + if (ranges[i].id_range_offset & 1) { + // Some font generators seem to put 65535 on id_range_offset + // for 0xFFFF-0xFFFF range. + // (e.g., many fonts in http://www.princexml.com/fonts/) + if (i == segcount - 1u) { + 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 Error("Bad segment offset (%d)", ranges[i].id_range_offset); + } + } + } + + // ranges must be ascending order, based on the end_code. Ranges may not + // overlap. + for (unsigned i = 1; i < segcount; ++i) { + if ((i == segcount - 1u) && + (ranges[i - 1].start_range == 0xffff) && + (ranges[i - 1].end_range == 0xffff) && + (ranges[i].start_range == 0xffff) && + (ranges[i].end_range == 0xffff)) { + // Some fonts (e.g., Germania.ttf) have multiple 0xffff terminators. + // We'll accept them as an exception. + 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 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 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 (os2->table.first_char_index != 0xFFFF && + ranges[i].start_range != 0xFFFF && + os2->table.first_char_index > ranges[i].start_range) { + os2->table.first_char_index = ranges[i].start_range; + } + if (os2->table.last_char_index != 0xFFFF && + ranges[i].end_range != 0xFFFF && + 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 Error("Final segment start and end must be 0xFFFF (0x%04X-0x%04X)", + ranges[segcount - 1].start_range, ranges[segcount - 1].end_range); + } + + // A format 4 CMAP subtable is complex. To be safe we simulate a lookup of + // each code-point defined in the table and make sure that they are all valid + // glyphs and that we don't access anything out-of-bounds. + for (unsigned i = 0; i < segcount; ++i) { + for (unsigned cp = ranges[i].start_range; cp <= ranges[i].end_range; ++cp) { + const uint16_t code_point = static_cast<uint16_t>(cp); + if (ranges[i].id_range_offset == 0) { + // this is explictly allowed to overflow in the spec + const uint16_t glyph = code_point + ranges[i].id_delta; + if (glyph >= num_glyphs) { + return Error("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1); + } + } else { + const uint16_t range_delta = code_point - ranges[i].start_range; + // this might seem odd, but it's true. The offset is relative to the + // location of the offset value itself. + const uint32_t glyph_id_offset = ranges[i].id_range_offset_offset + + ranges[i].id_range_offset + + range_delta * 2; + // We need to be able to access a 16-bit value from this offset + if (glyph_id_offset + 1 >= length) { + return Error("bad glyph id offset (%d > %ld)", glyph_id_offset, length); + } + uint16_t glyph; + std::memcpy(&glyph, data + glyph_id_offset, 2); + glyph = ots_ntohs(glyph); + if (glyph >= num_glyphs) { + return Error("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1); + } + } + } + } + + // We accept the table. + // TODO(yusukes): transcode the subtable. + if (platform == 3 && encoding == 0) { + this->subtable_3_0_4_data = data; + this->subtable_3_0_4_length = length; + } else if (platform == 3 && encoding == 1) { + this->subtable_3_1_4_data = data; + this->subtable_3_1_4_length = length; + } else if (platform == 0 && encoding == 3) { + this->subtable_0_3_4_data = data; + this->subtable_0_3_4_length = length; + } else { + return Error("Unknown cmap subtable type (platform=%d, encoding=%d)", platform, encoding); + } + + return true; +} + +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 Error("failed to skip the first 8 bytes of format 12 subtable"); + } + uint32_t language = 0; + if (!subtable.ReadU32(&language)) { + return Error("can't read format 12 subtable language"); + } + if (language) { + return Error("format 12 subtable language should be zero (%d)", language); + } + + uint32_t num_groups = 0; + if (!subtable.ReadU32(&num_groups)) { + return Error("can't read number of format 12 subtable groups"); + } + if (num_groups == 0 || subtable.remaining() / 12 < num_groups) { + return Error("Bad format 12 subtable group count %d", num_groups); + } + + std::vector<ots::OpenTypeCMAPSubtableRange> &groups + = 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 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 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 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 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 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 Error("overlapping format 12 subtable groups (startCharCode=0x%4X <= endCharCode=0x%4X of previous group)", + groups[i].start_range, groups[i-1].end_range); + } + } + + return true; +} + +bool 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 Error("Bad cmap subtable length"); + } + uint32_t language = 0; + if (!subtable.ReadU32(&language)) { + return Error("Can't read cmap subtable language"); + } + if (language) { + return Error("Cmap subtable language should be zero but is %d", language); + } + + uint32_t num_groups = 0; + if (!subtable.ReadU32(&num_groups)) { + 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 Error("Bad format 13 subtable group count %d", num_groups); + } + + std::vector<ots::OpenTypeCMAPSubtableRange> &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 Error("Can't read subrange structure in a cmap subtable"); + } + + // We conservatively limit all of the values to protect some parsers from + // overflows + if (groups[i].start_range > kUnicodeUpperLimit || + groups[i].end_range > kUnicodeUpperLimit || + groups[i].start_glyph_id > 0xFFFF) { + return 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 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 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 Error("Overlapping subranges (%d <= %d)", groups[i].start_range, groups[i-1].end_range); + } + } + + return true; +} + +bool OpenTypeCMAP::Parse0514(const uint8_t *data, size_t length) { + // Unicode Variation Selector table + ots::Buffer subtable(data, length); + + // Format 14 tables are simple. We parse these and fully serialise them + // later. + + // Skip format (USHORT) and length (ULONG) + if (!subtable.Skip(6)) { + return Error("Can't read start of cmap subtable"); + } + + uint32_t num_records = 0; + if (!subtable.ReadU32(&num_records)) { + return Error("Can't read number of records in cmap subtable"); + } + if (num_records == 0 || num_records > kMaxCMAPSelectorRecords) { + return Error("Bad format 14 subtable records count %d", num_records); + } + + std::vector<ots::OpenTypeCMAPSubtableVSRecord>& records + = 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 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 && + records[i].var_selector <= kMongolianVSEnd) || + (records[i].var_selector >= kVSStart && + records[i].var_selector <= kVSEnd) || + (records[i].var_selector >= kIVSStart && + records[i].var_selector <= kIVSEnd))) { + return 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 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 Error("No default aoffset in variation selector record %d", i); + } + if (records[i].default_offset && + records[i].default_offset >= length) { + 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 Error("Non default offset too high (%d >= %ld) in record %d", records[i].non_default_offset, length, i); + } + } + + for (unsigned i = 0; i < num_records; ++i) { + // Checks default UVS table + if (records[i].default_offset) { + subtable.set_offset(records[i].default_offset); + uint32_t num_ranges = 0; + if (!subtable.ReadU32(&num_ranges)) { + return Error("Can't read number of ranges in record %d", i); + } + if (num_ranges == 0 || subtable.remaining() / 4 < num_ranges) { + return Error("Bad number of ranges (%d) in record %d", num_ranges, i); + } + + uint32_t last_unicode_value = 0; + std::vector<ots::OpenTypeCMAPSubtableVSRange>& ranges + = records[i].ranges; + ranges.resize(num_ranges); + + for (unsigned j = 0; j < num_ranges; ++j) { + if (!subtable.ReadU24(&ranges[j].unicode_value) || + !subtable.ReadU8(&ranges[j].additional_count)) { + return 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; + if (ranges[j].unicode_value == 0 || + ranges[j].unicode_value > kUnicodeUpperLimit || + check_value > kUVSUpperLimit || + (last_unicode_value && + ranges[j].unicode_value <= last_unicode_value)) { + return Error("Bad Unicode value *%04X) in variation selector range %d record %d", ranges[j].unicode_value, j, i); + } + last_unicode_value = check_value; + } + } + + // Checks non default UVS table + if (records[i].non_default_offset) { + subtable.set_offset(records[i].non_default_offset); + uint32_t num_mappings = 0; + if (!subtable.ReadU32(&num_mappings)) { + return Error("Can't read number of mappings in variation selector record %d", i); + } + if (num_mappings == 0 || subtable.remaining() / 5 < num_mappings) { + return Error("Bad number of mappings (%d) in variation selector record %d", num_mappings, i); + } + + uint32_t last_unicode_value = 0; + std::vector<ots::OpenTypeCMAPSubtableVSMapping>& mappings + = records[i].mappings; + mappings.resize(num_mappings); + + for (unsigned j = 0; j < num_mappings; ++j) { + if (!subtable.ReadU24(&mappings[j].unicode_value) || + !subtable.ReadU16(&mappings[j].glyph_id)) { + return Error("Can't read mapping %d in variation selector record %d", j, i); + } + if (mappings[j].glyph_id == 0 || mappings[j].unicode_value == 0) { + return Error("Bad mapping (%04X -> %d) in mapping %d of variation selector %d", mappings[j].unicode_value, mappings[j].glyph_id, j, i); + } + if (mappings[j].unicode_value > kUnicodeUpperLimit) { + return Error("Invalid Unicode value (%04X > %04X) in mapping %d of variation selector %d", mappings[j].unicode_value, kUnicodeUpperLimit, j, i); + } + if (last_unicode_value && + mappings[j].unicode_value <= last_unicode_value) { + return Error("Out of order Unicode value (%04X <= %04X) in mapping %d of variation selector %d", mappings[j].unicode_value, last_unicode_value, j, i); + } + last_unicode_value = mappings[j].unicode_value; + } + } + } + + if (subtable.offset() != length) { + return Error("Bad subtable offset (%ld != %ld)", subtable.offset(), length); + } + this->subtable_0_5_14_length = subtable.offset(); + return true; +} + +bool OpenTypeCMAP::Parse100(const uint8_t *data, size_t length) { + // Mac Roman table + ots::Buffer subtable(data, length); + + if (!subtable.Skip(4)) { + return Error("Bad cmap subtable"); + } + uint16_t language = 0; + if (!subtable.ReadU16(&language)) { + return Error("Can't read language in cmap subtable"); + } + if (language) { + // simsun.ttf has non-zero language id. + Warning("language id should be zero: %u", language); + } + + 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 Error("Can't read glyph id at array[%ld] in cmap subtable", i); + } + this->subtable_1_0_0.push_back(glyph_id); + } + + return true; +} + +bool OpenTypeCMAP::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + uint16_t version = 0; + uint16_t num_tables = 0; + if (!table.ReadU16(&version) || + !table.ReadU16(&num_tables)) { + return Error("Can't read structure of cmap"); + } + + if (version != 0) { + return Error("Non zero cmap version (%d)", version); + } + if (!num_tables) { + return Error("No subtables in cmap!"); + } + + std::vector<CMAPSubtableHeader> subtable_headers; + + // read the subtable headers + subtable_headers.reserve(num_tables); + for (unsigned i = 0; i < num_tables; ++i) { + CMAPSubtableHeader subt; + + if (!table.ReadU16(&subt.platform) || + !table.ReadU16(&subt.encoding) || + !table.ReadU32(&subt.offset)) { + return Error("Can't read subtable information cmap subtable %d", i); + } + + subtable_headers.push_back(subt); + } + + const size_t data_offset = table.offset(); + + // make sure that all the offsets are valid. + for (unsigned i = 0; i < num_tables; ++i) { + if (subtable_headers[i].offset > 1024 * 1024 * 1024) { + return Error("Bad subtable offset in cmap subtable %d", i); + } + if (subtable_headers[i].offset < data_offset || + subtable_headers[i].offset >= length) { + return Error("Bad subtable offset (%d) in cmap subtable %d", subtable_headers[i].offset, i); + } + } + + // the format of the table is the first couple of bytes in the table. The + // length of the table is stored in a format-specific way. + for (unsigned i = 0; i < num_tables; ++i) { + table.set_offset(subtable_headers[i].offset); + if (!table.ReadU16(&subtable_headers[i].format)) { + return Error("Can't read cmap subtable header format %d", i); + } + + uint16_t len = 0; + uint16_t lang = 0; + switch (subtable_headers[i].format) { + case 0: + case 4: + if (!table.ReadU16(&len)) { + return Error("Can't read cmap subtable %d length", i); + } + if (!table.ReadU16(&lang)) { + return Error("Can't read cmap subtable %d language", i); + } + subtable_headers[i].length = len; + subtable_headers[i].language = lang; + break; + case 12: + case 13: + if (!table.Skip(2)) { + return Error("Bad cmap subtable %d structure", i); + } + if (!table.ReadU32(&subtable_headers[i].length)) { + return Error("Can read cmap subtable %d length", i); + } + if (!table.ReadU32(&subtable_headers[i].language)) { + return Error("Can't read cmap subtable %d language", i); + } + break; + case 14: + if (!table.ReadU32(&subtable_headers[i].length)) { + return Error("Can't read cmap subtable %d length", i); + } + subtable_headers[i].language = 0; + break; + default: + subtable_headers[i].length = 0; + subtable_headers[i].language = 0; + break; + } + } + + // check if the table is sorted first by platform ID, then by encoding ID. + for (unsigned i = 1; i < num_tables; ++i) { + if (subtable_headers[i - 1].platform > subtable_headers[i].platform || + (subtable_headers[i - 1].platform == subtable_headers[i].platform && + (subtable_headers[i - 1].encoding > subtable_headers[i].encoding || + (subtable_headers[i - 1].encoding == subtable_headers[i].encoding && + subtable_headers[i - 1].language > subtable_headers[i].language)))) + Warning("subtable %d with platform ID %d, encoding ID %d, language ID %d " + "following subtable with platform ID %d, encoding ID %d, language ID %d", + i, + subtable_headers[i].platform, + subtable_headers[i].encoding, + subtable_headers[i].language, + subtable_headers[i - 1].platform, + subtable_headers[i - 1].encoding, + subtable_headers[i - 1].language); + } + + // Now, verify that all the lengths are sane + for (unsigned i = 0; i < num_tables; ++i) { + if (!subtable_headers[i].length) continue; + if (subtable_headers[i].length > 1024 * 1024 * 1024) { + return 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 Error("Over long cmap subtable %d @ %d for %d", i, subtable_headers[i].offset, subtable_headers[i].length); + } + } + + // check that the cmap subtables are not overlapping. + std::set<std::pair<uint32_t, uint32_t> > uniq_checker; + std::vector<std::pair<uint32_t, uint8_t> > overlap_checker; + for (unsigned i = 0; i < num_tables; ++i) { + const uint32_t end_byte + = subtable_headers[i].offset + subtable_headers[i].length; + + if (!uniq_checker.insert(std::make_pair(subtable_headers[i].offset, + end_byte)).second) { + // Sometimes Unicode table and MS table share exactly the same data. + // We'll allow this. + continue; + } + overlap_checker.push_back( + std::make_pair(subtable_headers[i].offset, + static_cast<uint8_t>(1) /* start */)); + overlap_checker.push_back( + std::make_pair(end_byte, static_cast<uint8_t>(0) /* end */)); + } + std::sort(overlap_checker.begin(), overlap_checker.end()); + int overlap_count = 0; + for (unsigned i = 0; i < overlap_checker.size(); ++i) { + overlap_count += (overlap_checker[i].second ? 1 : -1); + if (overlap_count > 1) { + return 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. + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("No maxp table in font! Needed by cmap."); + } + 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 + // the UCS-4 table for non-BMP glyphs. We'll pass the following subtables: + // Platform ID Encoding ID Format + // 0 0 4 (Unicode Default) + // 0 1 4 (Unicode 1.1) + // 0 3 4 (Unicode BMP) + // 0 3 12 (Unicode UCS-4) + // 0 5 14 (Unicode Variation Sequences) + // 1 0 0 (Mac Roman) + // 3 0 4 (MS Symbol) + // 3 1 4 (MS Unicode BMP) + // 3 10 12 (MS Unicode UCS-4) + // 3 10 13 (MS UCS-4 Fallback mapping) + // + // Note: + // * 0-0-4 and 0-1-4 tables are (usually) written as a 3-1-4 table. If 3-1-4 table + // also exists, the 0-0-4 or 0-1-4 tables are ignored. + // * Unlike 0-0-4 table, 0-3-4 table is written as a 0-3-4 table. + // Some fonts which include 0-5-14 table seems to be required 0-3-4 + // table. The 0-3-4 table will be wriiten even if 3-1-4 table also exists. + // * 0-3-12 table is written as a 3-10-12 table. If 3-10-12 table also + // exists, the 0-3-12 table is ignored. + // + + for (unsigned i = 0; i < num_tables; ++i) { + if (subtable_headers[i].platform == 0) { + // Unicode platform + + if ((subtable_headers[i].encoding == 0 || subtable_headers[i].encoding == 1) && + (subtable_headers[i].format == 4)) { + // parse and output the 0-0-4 and 0-1-4 tables as 3-1-4 table. Sometimes the 0-0-4 + // table actually points to MS symbol data and thus should be parsed as + // 3-0-4 table (e.g., marqueem.ttf and quixotic.ttf). This error will be + // recovered in ots_cmap_serialise(). + if (!ParseFormat4(3, 1, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + 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(0, 3, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return Error("Failed to parse format 4 cmap subtable %d", i); + } + } else if ((subtable_headers[i].encoding == 3 || + subtable_headers[i].encoding == 4) && + (subtable_headers[i].format == 12)) { + // 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 Error("Failed to parse format 12 cmap subtable %d", i); + } + } else if ((subtable_headers[i].encoding == 5) && + (subtable_headers[i].format == 14)) { + if (!Parse0514(data + subtable_headers[i].offset, + subtable_headers[i].length)) { + return Error("Failed to parse format 14 cmap subtable %d", i); + } + } + } else if (subtable_headers[i].platform == 1) { + // Mac platform + + if ((subtable_headers[i].encoding == 0) && + (subtable_headers[i].format == 0)) { + // parse and output the 1-0-0 table. + if (!Parse100(data + subtable_headers[i].offset, + subtable_headers[i].length)) { + return OTS_FAILURE(); + } + } + } else if (subtable_headers[i].platform == 3) { + // MS platform + + switch (subtable_headers[i].encoding) { + case 0: + case 1: + if (subtable_headers[i].format == 4) { + // parse 3-0-4 or 3-1-4 table. + if (!ParseFormat4(subtable_headers[i].platform, + subtable_headers[i].encoding, + data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } + break; + case 10: + if (subtable_headers[i].format == 12) { + 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) { + this->subtable_3_10_13.clear(); + if (!Parse31013(data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } + break; + } + } + } + + return true; +} + +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) && 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<uint16_t>(have_034) + + static_cast<uint16_t>(have_0514) + + static_cast<uint16_t>(have_100) + + static_cast<uint16_t>(have_304) + + static_cast<uint16_t>(have_314) + + static_cast<uint16_t>(have_31012) + + static_cast<uint16_t>(have_31013); + const off_t table_start = out->Tell(); + + // Some fonts don't have 3-0-4 MS Symbol nor 3-1-4 Unicode BMP tables + // (e.g., old fonts for Mac). We don't support them. + if (!have_304 && !have_314 && !have_034 && !have_31012 && !have_31013) { + return Error("no supported subtables were found"); + } + + if (!out->WriteU16(0) || + !out->WriteU16(num_subtables)) { + return OTS_FAILURE(); + } + + const off_t record_offset = out->Tell(); + if (!out->Pad(num_subtables * 8)) { + return OTS_FAILURE(); + } + + const off_t offset_034 = out->Tell(); + if (have_034) { + if (!out->Write(this->subtable_0_3_4_data, + this->subtable_0_3_4_length)) { + return OTS_FAILURE(); + } + } + + const off_t offset_0514 = out->Tell(); + if (have_0514) { + const std::vector<ots::OpenTypeCMAPSubtableVSRecord> &records + = this->subtable_0_5_14; + const unsigned num_records = records.size(); + if (!out->WriteU16(14) || + !out->WriteU32(this->subtable_0_5_14_length) || + !out->WriteU32(num_records)) { + return OTS_FAILURE(); + } + for (unsigned i = 0; i < num_records; ++i) { + if (!out->WriteU24(records[i].var_selector) || + !out->WriteU32(records[i].default_offset) || + !out->WriteU32(records[i].non_default_offset)) { + return OTS_FAILURE(); + } + } + for (unsigned i = 0; i < num_records; ++i) { + if (records[i].default_offset) { + const std::vector<ots::OpenTypeCMAPSubtableVSRange> &ranges + = records[i].ranges; + const unsigned num_ranges = ranges.size(); + if (!out->Seek(records[i].default_offset + offset_0514) || + !out->WriteU32(num_ranges)) { + return OTS_FAILURE(); + } + for (unsigned j = 0; j < num_ranges; ++j) { + if (!out->WriteU24(ranges[j].unicode_value) || + !out->WriteU8(ranges[j].additional_count)) { + return OTS_FAILURE(); + } + } + } + if (records[i].non_default_offset) { + const std::vector<ots::OpenTypeCMAPSubtableVSMapping> &mappings + = records[i].mappings; + const unsigned num_mappings = mappings.size(); + if (!out->Seek(records[i].non_default_offset + offset_0514) || + !out->WriteU32(num_mappings)) { + return OTS_FAILURE(); + } + for (unsigned j = 0; j < num_mappings; ++j) { + if (!out->WriteU24(mappings[j].unicode_value) || + !out->WriteU16(mappings[j].glyph_id)) { + return OTS_FAILURE(); + } + } + } + } + } + + const off_t offset_100 = out->Tell(); + if (have_100) { + if (!out->WriteU16(0) || // format + !out->WriteU16(6 + kFormat0ArraySize) || // length + !out->WriteU16(0)) { // language + return OTS_FAILURE(); + } + if (!out->Write(&(this->subtable_1_0_0[0]), kFormat0ArraySize)) { + return OTS_FAILURE(); + } + } + + const off_t offset_304 = out->Tell(); + if (have_304) { + 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(this->subtable_3_1_4_data, + this->subtable_3_1_4_length)) { + return OTS_FAILURE(); + } + } + + const off_t offset_31012 = out->Tell(); + if (have_31012) { + std::vector<OpenTypeCMAPSubtableRange> &groups + = this->subtable_3_10_12; + const unsigned num_groups = groups.size(); + if (!out->WriteU16(12) || + !out->WriteU16(0) || + !out->WriteU32(num_groups * 12 + 16) || + !out->WriteU32(0) || + !out->WriteU32(num_groups)) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < num_groups; ++i) { + if (!out->WriteU32(groups[i].start_range) || + !out->WriteU32(groups[i].end_range) || + !out->WriteU32(groups[i].start_glyph_id)) { + return OTS_FAILURE(); + } + } + } + + const off_t offset_31013 = out->Tell(); + if (have_31013) { + std::vector<OpenTypeCMAPSubtableRange> &groups + = this->subtable_3_10_13; + const unsigned num_groups = groups.size(); + if (!out->WriteU16(13) || + !out->WriteU16(0) || + !out->WriteU32(num_groups * 12 + 16) || + !out->WriteU32(0) || + !out->WriteU32(num_groups)) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < num_groups; ++i) { + if (!out->WriteU32(groups[i].start_range) || + !out->WriteU32(groups[i].end_range) || + !out->WriteU32(groups[i].start_glyph_id)) { + return OTS_FAILURE(); + } + } + } + + const off_t table_end = out->Tell(); + + // Now seek back and write the table of offsets + if (!out->Seek(record_offset)) { + return OTS_FAILURE(); + } + + if (have_034) { + if (!out->WriteU16(0) || + !out->WriteU16(3) || + !out->WriteU32(offset_034 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_0514) { + if (!out->WriteU16(0) || + !out->WriteU16(5) || + !out->WriteU32(offset_0514 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_100) { + if (!out->WriteU16(1) || + !out->WriteU16(0) || + !out->WriteU32(offset_100 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_304) { + if (!out->WriteU16(3) || + !out->WriteU16(0) || + !out->WriteU32(offset_304 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_314) { + if (!out->WriteU16(3) || + !out->WriteU16(1) || + !out->WriteU32(offset_314 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_31012) { + if (!out->WriteU16(3) || + !out->WriteU16(10) || + !out->WriteU32(offset_31012 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_31013) { + if (!out->WriteU16(3) || + !out->WriteU16(10) || + !out->WriteU32(offset_31013 - table_start)) { + return OTS_FAILURE(); + } + } + + if (!out->Seek(table_end)) { + return OTS_FAILURE(); + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/cmap.h b/gfx/ots/src/cmap.h new file mode 100644 index 0000000000..0df24ee7e1 --- /dev/null +++ b/gfx/ots/src/cmap.h @@ -0,0 +1,87 @@ +// 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_CMAP_H_ +#define OTS_CMAP_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeCMAPSubtableRange { + uint32_t start_range; + uint32_t end_range; + uint32_t start_glyph_id; +}; + +struct OpenTypeCMAPSubtableVSRange { + uint32_t unicode_value; + uint8_t additional_count; +}; + +struct OpenTypeCMAPSubtableVSMapping { + uint32_t unicode_value; + uint16_t glyph_id; +}; + +struct OpenTypeCMAPSubtableVSRecord { + uint32_t var_selector; + uint32_t default_offset; + uint32_t non_default_offset; + std::vector<OpenTypeCMAPSubtableVSRange> ranges; + std::vector<OpenTypeCMAPSubtableVSMapping> mappings; +}; + +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), + subtable_3_0_4_length(0), + subtable_3_1_4_data(NULL), + 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; + + // Platform 0, Encoding 5, Format 14, Unicode Variation Sequence table. + size_t subtable_0_5_14_length; + std::vector<OpenTypeCMAPSubtableVSRecord> subtable_0_5_14; + + // Platform 3, Encoding 0, Format 4, MS Symbol table. + const uint8_t *subtable_3_0_4_data; + size_t subtable_3_0_4_length; + // Platform 3, Encoding 1, Format 4, MS Unicode BMP table. + const uint8_t *subtable_3_1_4_data; + size_t subtable_3_1_4_length; + + // Platform 3, Encoding 10, Format 12, MS Unicode UCS-4 table. + std::vector<OpenTypeCMAPSubtableRange> subtable_3_10_12; + // Platform 3, Encoding 10, Format 13, MS UCS-4 Fallback table. + std::vector<OpenTypeCMAPSubtableRange> subtable_3_10_13; + // Platform 1, Encoding 0, Format 0, Mac Roman table. + std::vector<uint8_t> subtable_1_0_0; + + 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); + bool Parse100(const uint8_t *data, size_t length); +}; + +} // namespace ots + +#endif diff --git a/gfx/ots/src/colr.cc b/gfx/ots/src/colr.cc new file mode 100644 index 0000000000..8931c77c32 --- /dev/null +++ b/gfx/ots/src/colr.cc @@ -0,0 +1,1092 @@ +// Copyright (c) 2022 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 "colr.h" + +#include "cpal.h" +#include "maxp.h" +#include "variations.h" + +#include <map> +#include <set> +#include <vector> + +// COLR - Color Table +// http://www.microsoft.com/typography/otspec/colr.htm + +#define TABLE_NAME "COLR" + +namespace { + +// Some typedefs so that our local variables will more closely parallel the spec. +typedef int16_t F2DOT14; // 2.14 fixed-point +typedef int32_t Fixed; // 16.16 fixed-point +typedef int16_t FWORD; // A 16-bit integer value in font design units +typedef uint16_t UFWORD; +typedef uint32_t VarIdxBase; + +constexpr F2DOT14 F2DOT14_one = 0x4000; + +struct colrState +{ + // We track addresses of structs that we have already seen and checked, + // because fonts may share these among multiple glyph descriptions. + // (We only do this for color lines, which may be large, depending on the + // number of color stops, and for paints, which may refer to an extensive + // sub-graph; for small records like ClipBox and Affine2x3, we just read + // them directly whenever encountered.) + std::set<const uint8_t*> colorLines; + std::set<const uint8_t*> varColorLines; + std::set<const uint8_t*> paints; + + std::map<uint16_t, std::pair<const uint8_t*, size_t>> baseGlyphMap; + std::vector<std::pair<const uint8_t*, size_t>> layerList; + + // Set of visited records, for cycle detection. + // We don't actually need to track every paint table here, as most of the + // paint-offsets that create the graph can only point forwards; + // only PaintColrLayers and PaintColrGlyph can cause a backward jump + // and hence a potential cycle. + std::set<const uint8_t*> visited; + + uint16_t numGlyphs; // from maxp + uint16_t numPaletteEntries; // from CPAL +}; + +// std::set<T>::contains isn't available until C++20, so we use this instead +// for better compatibility with old compilers. +template <typename T> +bool setContains(const std::set<T>& set, T item) +{ + return set.find(item) != set.end(); +} + +enum Extend : uint8_t +{ + EXTEND_PAD = 0, + EXTEND_REPEAT = 1, + EXTEND_REFLECT = 2, +}; + +bool ParseColorLine(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, bool var) +{ + auto& set = var ? state.varColorLines : state.colorLines; + if (setContains(set, data)) { + return true; + } + set.insert(data); + + ots::Buffer subtable(data, length); + + uint8_t extend; + uint16_t numColorStops; + + if (!subtable.ReadU8(&extend) || + !subtable.ReadU16(&numColorStops)) { + return OTS_FAILURE_MSG("Failed to read [Var]ColorLine"); + } + + if (extend != EXTEND_PAD && extend != EXTEND_REPEAT && extend != EXTEND_REFLECT) { + OTS_WARNING("Unknown color-line extend mode %u", extend); + } + + for (auto i = 0u; i < numColorStops; ++i) { + F2DOT14 stopOffset; + uint16_t paletteIndex; + F2DOT14 alpha; + VarIdxBase varIndexBase; + + if (!subtable.ReadS16(&stopOffset) || + !subtable.ReadU16(&paletteIndex) || + !subtable.ReadS16(&alpha) || + (var && !subtable.ReadU32(&varIndexBase))) { + return OTS_FAILURE_MSG("Failed to read [Var]ColorStop"); + } + + if (paletteIndex >= state.numPaletteEntries && paletteIndex != 0xffffu) { + return OTS_FAILURE_MSG("Invalid palette index %u in color stop", paletteIndex); + } + + if (alpha < 0 || alpha > F2DOT14_one) { + OTS_WARNING("Alpha value outside valid range 0.0 - 1.0"); + } + } + + return true; +} + +// Composition modes +enum CompositeMode : uint8_t +{ + // Porter-Duff modes + // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators + COMPOSITE_CLEAR = 0, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_clear + COMPOSITE_SRC = 1, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_src + COMPOSITE_DEST = 2, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dst + COMPOSITE_SRC_OVER = 3, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcover + COMPOSITE_DEST_OVER = 4, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstover + COMPOSITE_SRC_IN = 5, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcin + COMPOSITE_DEST_IN = 6, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstin + COMPOSITE_SRC_OUT = 7, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcout + COMPOSITE_DEST_OUT = 8, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstout + COMPOSITE_SRC_ATOP = 9, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_srcatop + COMPOSITE_DEST_ATOP = 10, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_dstatop + COMPOSITE_XOR = 11, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_xor + COMPOSITE_PLUS = 12, // https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators_plus + + // Blend modes + // https://www.w3.org/TR/compositing-1/#blending + COMPOSITE_SCREEN = 13, // https://www.w3.org/TR/compositing-1/#blendingscreen + COMPOSITE_OVERLAY = 14, // https://www.w3.org/TR/compositing-1/#blendingoverlay + COMPOSITE_DARKEN = 15, // https://www.w3.org/TR/compositing-1/#blendingdarken + COMPOSITE_LIGHTEN = 16, // https://www.w3.org/TR/compositing-1/#blendinglighten + COMPOSITE_COLOR_DODGE = 17, // https://www.w3.org/TR/compositing-1/#blendingcolordodge + COMPOSITE_COLOR_BURN = 18, // https://www.w3.org/TR/compositing-1/#blendingcolorburn + COMPOSITE_HARD_LIGHT = 19, // https://www.w3.org/TR/compositing-1/#blendinghardlight + COMPOSITE_SOFT_LIGHT = 20, // https://www.w3.org/TR/compositing-1/#blendingsoftlight + COMPOSITE_DIFFERENCE = 21, // https://www.w3.org/TR/compositing-1/#blendingdifference + COMPOSITE_EXCLUSION = 22, // https://www.w3.org/TR/compositing-1/#blendingexclusion + COMPOSITE_MULTIPLY = 23, // https://www.w3.org/TR/compositing-1/#blendingmultiply + + // Modes that, uniquely, do not operate on components + // https://www.w3.org/TR/compositing-1/#blendingnonseparable + COMPOSITE_HSL_HUE = 24, // https://www.w3.org/TR/compositing-1/#blendinghue + COMPOSITE_HSL_SATURATION = 25, // https://www.w3.org/TR/compositing-1/#blendingsaturation + COMPOSITE_HSL_COLOR = 26, // https://www.w3.org/TR/compositing-1/#blendingcolor + COMPOSITE_HSL_LUMINOSITY = 27, // https://www.w3.org/TR/compositing-1/#blendingluminosity +}; + +bool ParseAffine(const ots::Font* font, + const uint8_t* data, size_t length, + bool var) +{ + ots::Buffer subtable(data, length); + + Fixed xx, yx, xy, yy, dx, dy; + VarIdxBase varIndexBase; + + if (!subtable.ReadS32(&xx) || + !subtable.ReadS32(&yx) || + !subtable.ReadS32(&xy) || + !subtable.ReadS32(&yy) || + !subtable.ReadS32(&dx) || + !subtable.ReadS32(&dy) || + (var && !subtable.ReadU32(&varIndexBase))) { + return OTS_FAILURE_MSG("Failed to read [Var]Affine2x3"); + } + + return true; +} + +// Paint-record dispatch function that reads the format byte and then dispatches +// to one of the record-specific helpers. +bool ParsePaint(const ots::Font* font, const uint8_t* data, size_t length, colrState& state); + +// All these paint record parsers start with Skip(1) to ignore the format field, +// which the caller has already read in order to dispatch here. + +bool ParsePaintColrLayers(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state) +{ + if (setContains(state.visited, data)) { + return OTS_FAILURE_MSG("Cycle detected in PaintColrLayers"); + } + state.visited.insert(data); + + ots::Buffer subtable(data, length); + + uint8_t numLayers; + uint32_t firstLayerIndex; + + if (!subtable.Skip(1) || + !subtable.ReadU8(&numLayers) || + !subtable.ReadU32(&firstLayerIndex)) { + return OTS_FAILURE_MSG("Failed to read PaintColrLayers record"); + } + + if (uint64_t(firstLayerIndex) + numLayers > state.layerList.size()) { + return OTS_FAILURE_MSG("PaintColrLayers exceeds bounds of layer list"); + } + + for (auto i = firstLayerIndex; i < firstLayerIndex + numLayers; ++i) { + auto layer = state.layerList[i]; + if (!ParsePaint(font, layer.first, layer.second, state)) { + return OTS_FAILURE_MSG("Failed to parse layer"); + } + } + + state.visited.erase(data); + + return true; +} + +bool ParsePaintSolid(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, bool var) +{ + ots::Buffer subtable(data, length); + + uint16_t paletteIndex; + F2DOT14 alpha; + + if (!subtable.Skip(1) || + !subtable.ReadU16(&paletteIndex) || + !subtable.ReadS16(&alpha)) { + return OTS_FAILURE_MSG("Failed to read PaintSolid"); + } + + if (paletteIndex >= state.numPaletteEntries && paletteIndex != 0xffffu) { + return OTS_FAILURE_MSG("Invalid palette index %u PaintSolid", paletteIndex); + } + + if (alpha < 0 || alpha > F2DOT14_one) { + OTS_WARNING("Alpha value outside valid range 0.0 - 1.0"); + } + + if (var) { + VarIdxBase varIndexBase; + if (!subtable.ReadU32(&varIndexBase)) { + return OTS_FAILURE_MSG("Failed to read PaintVarSolid"); + } + } + + return true; +} + +bool ParsePaintLinearGradient(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, bool var) +{ + ots::Buffer subtable(data, length); + + uint32_t colorLineOffset; + FWORD x0, y0, x1, y1, x2, y2; + VarIdxBase varIndexBase; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&colorLineOffset) || + !subtable.ReadS16(&x0) || + !subtable.ReadS16(&y0) || + !subtable.ReadS16(&x1) || + !subtable.ReadS16(&y1) || + !subtable.ReadS16(&x2) || + !subtable.ReadS16(&y2) || + (var && !subtable.ReadU32(&varIndexBase))) { + return OTS_FAILURE_MSG("Failed to read Paint[Var]LinearGradient"); + } + + if (colorLineOffset >= length) { + return OTS_FAILURE_MSG("ColorLine is out of bounds"); + } + + if (!ParseColorLine(font, data + colorLineOffset, length - colorLineOffset, state, var)) { + return OTS_FAILURE_MSG("Failed to parse [Var]ColorLine"); + } + + return true; +} + +bool ParsePaintRadialGradient(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, bool var) +{ + ots::Buffer subtable(data, length); + + uint32_t colorLineOffset; + FWORD x0, y0; + UFWORD radius0; + FWORD x1, y1; + UFWORD radius1; + VarIdxBase varIndexBase; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&colorLineOffset) || + !subtable.ReadS16(&x0) || + !subtable.ReadS16(&y0) || + !subtable.ReadU16(&radius0) || + !subtable.ReadS16(&x1) || + !subtable.ReadS16(&y1) || + !subtable.ReadU16(&radius1) || + (var && !subtable.ReadU32(&varIndexBase))) { + return OTS_FAILURE_MSG("Failed to read Paint[Var]RadialGradient"); + } + + if (colorLineOffset >= length) { + return OTS_FAILURE_MSG("ColorLine is out of bounds"); + } + + if (!ParseColorLine(font, data + colorLineOffset, length - colorLineOffset, state, var)) { + return OTS_FAILURE_MSG("Failed to parse [Var]ColorLine"); + } + + return true; +} + +bool ParsePaintSweepGradient(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, bool var) +{ + ots::Buffer subtable(data, length); + + uint32_t colorLineOffset; + FWORD centerX, centerY; + F2DOT14 startAngle, endAngle; + VarIdxBase varIndexBase; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&colorLineOffset) || + !subtable.ReadS16(¢erX) || + !subtable.ReadS16(¢erY) || + !subtable.ReadS16(&startAngle) || + !subtable.ReadS16(&endAngle) || + (var && !subtable.ReadU32(&varIndexBase))) { + return OTS_FAILURE_MSG("Failed to read Paint[Var]SweepGradient"); + } + + if (colorLineOffset >= length) { + return OTS_FAILURE_MSG("ColorLine is out of bounds"); + } + + if (!ParseColorLine(font, data + colorLineOffset, length - colorLineOffset, state, var)) { + return OTS_FAILURE_MSG("Failed to parse [Var]ColorLine"); + } + + return true; +} + +bool ParsePaintGlyph(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state) +{ + ots::Buffer subtable(data, length); + + uint32_t paintOffset; + uint16_t glyphID; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&paintOffset) || + !subtable.ReadU16(&glyphID)) { + return OTS_FAILURE_MSG("Failed to read PaintGlyph"); + } + + if (!paintOffset || paintOffset >= length) { + return OTS_FAILURE_MSG("Invalid paint offset in PaintGlyph"); + } + + if (glyphID >= state.numGlyphs) { + return OTS_FAILURE_MSG("Glyph ID %u out of bounds", glyphID); + } + + if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) { + return OTS_FAILURE_MSG("Failed to parse paint for PaintGlyph"); + } + + return true; +} + +bool ParsePaintColrGlyph(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state) +{ + if (setContains(state.visited, data)) { + return OTS_FAILURE_MSG("Cycle detected in PaintColrGlyph"); + } + state.visited.insert(data); + + ots::Buffer subtable(data, length); + + uint16_t glyphID; + + if (!subtable.Skip(1) || + !subtable.ReadU16(&glyphID)) { + return OTS_FAILURE_MSG("Failed to read PaintColrGlyph"); + } + + auto baseGlyph = state.baseGlyphMap.find(glyphID); + if (baseGlyph == state.baseGlyphMap.end()) { + return OTS_FAILURE_MSG("Glyph ID %u not found in BaseGlyphList", glyphID); + } + + if (!ParsePaint(font, baseGlyph->second.first, baseGlyph->second.second, state)) { + return OTS_FAILURE_MSG("Failed to parse referenced color glyph %u", glyphID); + } + + state.visited.erase(data); + + return true; +} + +bool ParsePaintTransform(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, bool var) +{ + ots::Buffer subtable(data, length); + + uint32_t paintOffset; + uint32_t transformOffset; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&paintOffset) || + !subtable.ReadU24(&transformOffset)) { + return OTS_FAILURE_MSG("Failed to read Paint[Var]Transform"); + } + + if (!paintOffset || paintOffset >= length) { + return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Transform"); + } + if (transformOffset >= length) { + return OTS_FAILURE_MSG("Transform offset out of bounds"); + } + + if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) { + return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Transform"); + } + + if (!ParseAffine(font, data + transformOffset, length - transformOffset, var)) { + return OTS_FAILURE_MSG("Failed to parse affine transform"); + } + + return true; +} + +bool ParsePaintTranslate(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, bool var) +{ + ots::Buffer subtable(data, length); + + uint32_t paintOffset; + FWORD dx, dy; + VarIdxBase varIndexBase; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&paintOffset) || + !subtable.ReadS16(&dx) || + !subtable.ReadS16(&dy) || + (var && !subtable.ReadU32(&varIndexBase))) { + return OTS_FAILURE_MSG("Failed to read Paint[Var]Translate"); + } + + if (!paintOffset || paintOffset >= length) { + return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Translate"); + } + + if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) { + return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Translate"); + } + + return true; +} + +bool ParsePaintScale(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, + bool var, bool aroundCenter, bool uniform) +{ + ots::Buffer subtable(data, length); + + uint32_t paintOffset; + F2DOT14 scaleX, scaleY; + FWORD centerX, centerY; + VarIdxBase varIndexBase; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&paintOffset) || + !subtable.ReadS16(&scaleX) || + (!uniform && !subtable.ReadS16(&scaleY)) || + (aroundCenter && (!subtable.ReadS16(¢erX) || + !subtable.ReadS16(¢erY))) || + (var && !subtable.ReadU32(&varIndexBase))) { + return OTS_FAILURE_MSG("Failed to read Paint[Var]Scale[...]"); + } + + if (!paintOffset || paintOffset >= length) { + return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Scale[...]"); + } + + if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) { + return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Scale[...]"); + } + + return true; +} + +bool ParsePaintRotate(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, + bool var, bool aroundCenter) +{ + ots::Buffer subtable(data, length); + + uint32_t paintOffset; + F2DOT14 angle; + FWORD centerX, centerY; + VarIdxBase varIndexBase; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&paintOffset) || + !subtable.ReadS16(&angle) || + (aroundCenter && (!subtable.ReadS16(¢erX) || + !subtable.ReadS16(¢erY))) || + (var && !subtable.ReadU32(&varIndexBase))) { + return OTS_FAILURE_MSG("Failed to read Paint[Var]Rotate[...]"); + } + + if (!paintOffset || paintOffset >= length) { + return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Rotate[...]"); + } + + if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) { + return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Rotate[...]"); + } + + return true; +} + +bool ParsePaintSkew(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state, + bool var, bool aroundCenter) +{ + ots::Buffer subtable(data, length); + + uint32_t paintOffset; + F2DOT14 xSkewAngle, ySkewAngle; + FWORD centerX, centerY; + VarIdxBase varIndexBase; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&paintOffset) || + !subtable.ReadS16(&xSkewAngle) || + !subtable.ReadS16(&ySkewAngle) || + (aroundCenter && (!subtable.ReadS16(¢erX) || + !subtable.ReadS16(¢erY))) || + (var && !subtable.ReadU32(&varIndexBase))) { + return OTS_FAILURE_MSG("Failed to read Paint[Var]Skew[...]"); + } + + if (!paintOffset || paintOffset >= length) { + return OTS_FAILURE_MSG("Invalid paint offset in Paint[Var]Skew[...]"); + } + + if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) { + return OTS_FAILURE_MSG("Failed to parse paint for Paint[Var]Skew[...]"); + } + + return true; +} + +bool ParsePaintComposite(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state) +{ + ots::Buffer subtable(data, length); + + uint32_t sourcePaintOffset; + uint8_t compositeMode; + uint32_t backdropPaintOffset; + + if (!subtable.Skip(1) || + !subtable.ReadU24(&sourcePaintOffset) || + !subtable.ReadU8(&compositeMode) || + !subtable.ReadU24(&backdropPaintOffset)) { + return OTS_FAILURE_MSG("Failed to read PaintComposite"); + } + if (compositeMode > COMPOSITE_HSL_LUMINOSITY) { + OTS_WARNING("Unknown composite mode %u\n", compositeMode); + } + + if (!sourcePaintOffset || sourcePaintOffset >= length) { + return OTS_FAILURE_MSG("Invalid source paint offset"); + } + if (!ParsePaint(font, data + sourcePaintOffset, length - sourcePaintOffset, state)) { + return OTS_FAILURE_MSG("Failed to parse source paint"); + } + + if (!backdropPaintOffset || backdropPaintOffset >= length) { + return OTS_FAILURE_MSG("Invalid backdrop paint offset"); + } + if (!ParsePaint(font, data + backdropPaintOffset, length - backdropPaintOffset, state)) { + return OTS_FAILURE_MSG("Failed to parse backdrop paint"); + } + + return true; +} + +bool ParsePaint(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state) +{ + if (setContains(state.paints, data)) { + return true; + } + + ots::Buffer subtable(data, length); + + uint8_t format; + + if (!subtable.ReadU8(&format)) { + return OTS_FAILURE_MSG("Failed to read paint record format"); + } + + bool ok = true; + switch (format) { + case 1: ok = ParsePaintColrLayers(font, data, length, state); break; + case 2: ok = ParsePaintSolid(font, data, length, state, false); break; + case 3: ok = ParsePaintSolid(font, data, length, state, true); break; + case 4: ok = ParsePaintLinearGradient(font, data, length, state, false); break; + case 5: ok = ParsePaintLinearGradient(font, data, length, state, true); break; + case 6: ok = ParsePaintRadialGradient(font, data, length, state, false); break; + case 7: ok = ParsePaintRadialGradient(font, data, length, state, true); break; + case 8: ok = ParsePaintSweepGradient(font, data, length, state, false); break; + case 9: ok = ParsePaintSweepGradient(font, data, length, state, true); break; + case 10: ok = ParsePaintGlyph(font, data, length, state); break; + case 11: ok = ParsePaintColrGlyph(font, data, length, state); break; + case 12: ok = ParsePaintTransform(font, data, length, state, false); break; + case 13: ok = ParsePaintTransform(font, data, length, state, true); break; + case 14: ok = ParsePaintTranslate(font, data, length, state, false); break; + case 15: ok = ParsePaintTranslate(font, data, length, state, true); break; + case 16: ok = ParsePaintScale(font, data, length, state, false, false, false); break; // Scale + case 17: ok = ParsePaintScale(font, data, length, state, true, false, false); break; // VarScale + case 18: ok = ParsePaintScale(font, data, length, state, false, true, false); break; // ScaleAroundCenter + case 19: ok = ParsePaintScale(font, data, length, state, true, true, false); break; // VarScaleAroundCenter + case 20: ok = ParsePaintScale(font, data, length, state, false, false, true); break; // ScaleUniform + case 21: ok = ParsePaintScale(font, data, length, state, true, false, true); break; // VarScaleUniform + case 22: ok = ParsePaintScale(font, data, length, state, false, true, true); break; // ScaleUniformAroundCenter + case 23: ok = ParsePaintScale(font, data, length, state, true, true, true); break; // VarScaleUniformAroundCenter + case 24: ok = ParsePaintRotate(font, data, length, state, false, false); break; // Rotate + case 25: ok = ParsePaintRotate(font, data, length, state, true, false); break; // VarRotate + case 26: ok = ParsePaintRotate(font, data, length, state, false, true); break; // RotateAroundCenter + case 27: ok = ParsePaintRotate(font, data, length, state, true, true); break; // VarRotateAroundCenter + case 28: ok = ParsePaintSkew(font, data, length, state, false, false); break; // Skew + case 29: ok = ParsePaintSkew(font, data, length, state, true, false); break; // VarSkew + case 30: ok = ParsePaintSkew(font, data, length, state, false, true); break; // SkewAroundCenter + case 31: ok = ParsePaintSkew(font, data, length, state, true, true); break; // VarSkewAroundCenter + case 32: ok = ParsePaintComposite(font, data, length, state); break; + default: + // Clients are supposed to ignore unknown paint types. + OTS_WARNING("Unknown paint type %u", format); + break; + } + + state.paints.insert(data); + + return ok; +} + +#pragma pack(1) +struct COLRv1 +{ + // Version-0 fields + uint16_t version; + uint16_t numBaseGlyphRecords; + uint32_t baseGlyphRecordsOffset; + uint32_t layerRecordsOffset; + uint16_t numLayerRecords; + // Version-1 additions + uint32_t baseGlyphListOffset; + uint32_t layerListOffset; + uint32_t clipListOffset; + uint32_t varIdxMapOffset; + uint32_t varStoreOffset; +}; +#pragma pack() + +bool ParseBaseGlyphRecords(const ots::Font* font, + const uint8_t* data, size_t length, + uint32_t numBaseGlyphRecords, + uint32_t numLayerRecords, + colrState& state) +{ + ots::Buffer subtable(data, length); + + int32_t prevGlyphID = -1; + for (auto i = 0u; i < numBaseGlyphRecords; ++i) { + uint16_t glyphID, + firstLayerIndex, + numLayers; + + if (!subtable.ReadU16(&glyphID) || + !subtable.ReadU16(&firstLayerIndex) || + !subtable.ReadU16(&numLayers)) { + return OTS_FAILURE_MSG("Failed to read base glyph record"); + } + + if (glyphID >= int32_t(state.numGlyphs)) { + return OTS_FAILURE_MSG("Base glyph record glyph ID %u out of bounds", glyphID); + } + + if (int32_t(glyphID) <= prevGlyphID) { + return OTS_FAILURE_MSG("Base glyph record for glyph ID %u out of order", glyphID); + } + + if (uint32_t(firstLayerIndex) + uint32_t(numLayers) > numLayerRecords) { + return OTS_FAILURE_MSG("Layer index out of bounds"); + } + + prevGlyphID = glyphID; + } + + return true; +} + +bool ParseLayerRecords(const ots::Font* font, + const uint8_t* data, size_t length, + uint32_t numLayerRecords, + colrState& state) +{ + ots::Buffer subtable(data, length); + + for (auto i = 0u; i < numLayerRecords; ++i) { + uint16_t glyphID, + paletteIndex; + + if (!subtable.ReadU16(&glyphID) || + !subtable.ReadU16(&paletteIndex)) { + return OTS_FAILURE_MSG("Failed to read layer record"); + } + + if (glyphID >= int32_t(state.numGlyphs)) { + return OTS_FAILURE_MSG("Layer record glyph ID %u out of bounds", glyphID); + } + + if (paletteIndex >= state.numPaletteEntries && paletteIndex != 0xffffu) { + return OTS_FAILURE_MSG("Invalid palette index %u in layer record", paletteIndex); + } + } + + return true; +} + +bool ParseBaseGlyphList(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state) +{ + ots::Buffer subtable(data, length); + + uint32_t numBaseGlyphPaintRecords; + + if (!subtable.ReadU32(&numBaseGlyphPaintRecords)) { + return OTS_FAILURE_MSG("Failed to read base glyph list"); + } + + int32_t prevGlyphID = -1; + // We loop over the list twice, first to collect all the glyph IDs present, + // and then to check they can be parsed. + size_t saveOffset = subtable.offset(); + for (auto i = 0u; i < numBaseGlyphPaintRecords; ++i) { + uint16_t glyphID; + uint32_t paintOffset; + + if (!subtable.ReadU16(&glyphID) || + !subtable.ReadU32(&paintOffset)) { + return OTS_FAILURE_MSG("Failed to read base glyph list"); + } + + if (glyphID >= int32_t(state.numGlyphs)) { + return OTS_FAILURE_MSG("Base glyph list glyph ID %u out of bounds", glyphID); + } + + if (int32_t(glyphID) <= prevGlyphID) { + return OTS_FAILURE_MSG("Base glyph list record for glyph ID %u out of order", glyphID); + } + + if (!paintOffset || paintOffset >= length) { + return OTS_FAILURE_MSG("Invalid paint offset for base glyph ID %u", glyphID); + } + + // Record the base glyph list records so that we can follow them when processing + // PaintColrGlyph records. + state.baseGlyphMap[glyphID] = std::pair<const uint8_t*, size_t>(data + paintOffset, length - paintOffset); + prevGlyphID = glyphID; + } + + subtable.set_offset(saveOffset); + for (auto i = 0u; i < numBaseGlyphPaintRecords; ++i) { + uint16_t glyphID; + uint32_t paintOffset; + + if (!subtable.ReadU16(&glyphID) || + !subtable.ReadU32(&paintOffset)) { + return OTS_FAILURE_MSG("Failed to read base glyph list"); + } + + if (!ParsePaint(font, data + paintOffset, length - paintOffset, state)) { + return OTS_FAILURE_MSG("Failed to parse paint for base glyph ID %u", glyphID); + } + + // After each base glyph record is fully processed, the visited set should be clear; + // otherwise, we have a bug in the cycle-detection logic. + assert(state.visited.empty()); + } + + return true; +} + +// We call this twice: first with parsePaints = false, to just get the number of layers, +// and then with parsePaints = true to actually descend the paint graphs. +bool ParseLayerList(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state) +{ + ots::Buffer subtable(data, length); + + uint32_t numLayers; + if (!subtable.ReadU32(&numLayers)) { + return OTS_FAILURE_MSG("Failed to read layer list"); + } + + for (auto i = 0u; i < numLayers; ++i) { + uint32_t paintOffset; + + if (!subtable.ReadU32(&paintOffset)) { + return OTS_FAILURE_MSG("Failed to read layer list"); + } + + if (!paintOffset || paintOffset >= length) { + return OTS_FAILURE_MSG("Invalid paint offset in layer list"); + } + + state.layerList.push_back(std::pair<const uint8_t*, size_t>(data + paintOffset, length - paintOffset)); + } + + return true; +} + +bool ParseClipBox(const ots::Font* font, + const uint8_t* data, size_t length) +{ + ots::Buffer subtable(data, length); + + uint8_t format; + FWORD xMin, yMin, xMax, yMax; + + if (!subtable.ReadU8(&format) || + !subtable.ReadS16(&xMin) || + !subtable.ReadS16(&yMin) || + !subtable.ReadS16(&xMax) || + !subtable.ReadS16(&yMax)) { + return OTS_FAILURE_MSG("Failed to read clip box"); + } + + switch (format) { + case 1: + break; + case 2: { + uint32_t varIndexBase; + if (!subtable.ReadU32(&varIndexBase)) { + return OTS_FAILURE_MSG("Failed to read clip box"); + } + break; + } + default: + return OTS_FAILURE_MSG("Invalid clip box format: %u", format); + } + + if (xMin > xMax || yMin > yMax) { + return OTS_FAILURE_MSG("Invalid clip box bounds"); + } + + return true; +} + +bool ParseClipList(const ots::Font* font, + const uint8_t* data, size_t length, + colrState& state) +{ + ots::Buffer subtable(data, length); + + uint8_t format; + uint32_t numClipRecords; + + if (!subtable.ReadU8(&format) || + !subtable.ReadU32(&numClipRecords)) { + return OTS_FAILURE_MSG("Failed to read clip list"); + } + + if (format != 1) { + return OTS_FAILURE_MSG("Unknown clip list format: %u", format); + } + + int32_t prevEndGlyphID = -1; + for (auto i = 0u; i < numClipRecords; ++i) { + uint16_t startGlyphID, + endGlyphID; + uint32_t clipBoxOffset; + + if (!subtable.ReadU16(&startGlyphID) || + !subtable.ReadU16(&endGlyphID) || + !subtable.ReadU24(&clipBoxOffset)) { + return OTS_FAILURE_MSG("Failed to read clip list"); + } + + if (int32_t(startGlyphID) <= prevEndGlyphID || + endGlyphID < startGlyphID || + endGlyphID >= int32_t(state.numGlyphs)) { + return OTS_FAILURE_MSG("Bad or out-of-order glyph ID range %u-%u in clip list", startGlyphID, endGlyphID); + } + + if (clipBoxOffset >= length) { + return OTS_FAILURE_MSG("Clip box offset out of bounds for glyphs %u-%u", startGlyphID, endGlyphID); + } + + if (!ParseClipBox(font, data + clipBoxOffset, length - clipBoxOffset)) { + return OTS_FAILURE_MSG("Failed to parse clip box for glyphs %u-%u", startGlyphID, endGlyphID); + } + + prevEndGlyphID = endGlyphID; + } + + return true; +} + +} // namespace + +namespace ots { + +bool OpenTypeCOLR::Parse(const uint8_t *data, size_t length) { + // Parsing COLR table requires |maxp->num_glyphs| and |cpal->num_palette_entries|. + Font *font = GetFont(); + Buffer table(data, length); + + uint32_t headerSize = offsetof(COLRv1, baseGlyphListOffset); + + // Version 0 header fields. + uint16_t version = 0; + uint16_t numBaseGlyphRecords = 0; + uint32_t offsetBaseGlyphRecords = 0; + uint32_t offsetLayerRecords = 0; + uint16_t numLayerRecords = 0; + if (!table.ReadU16(&version) || + !table.ReadU16(&numBaseGlyphRecords) || + !table.ReadU32(&offsetBaseGlyphRecords) || + !table.ReadU32(&offsetLayerRecords) || + !table.ReadU16(&numLayerRecords)) { + return Error("Incomplete table"); + } + + if (version > 1) { + return Error("Unknown COLR table version %u", version); + } + + // Additional header fields for Version 1. + uint32_t offsetBaseGlyphList = 0; + uint32_t offsetLayerList = 0; + uint32_t offsetClipList = 0; + uint32_t offsetVarIdxMap = 0; + uint32_t offsetItemVariationStore = 0; + + if (version == 1) { + if (!table.ReadU32(&offsetBaseGlyphList) || + !table.ReadU32(&offsetLayerList) || + !table.ReadU32(&offsetClipList) || + !table.ReadU32(&offsetVarIdxMap) || + !table.ReadU32(&offsetItemVariationStore)) { + return Error("Incomplete v.1 table"); + } + headerSize = sizeof(COLRv1); + } + + colrState state; + + auto* maxp = static_cast<ots::OpenTypeMAXP*>(font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return OTS_FAILURE_MSG("Required maxp table missing"); + } + state.numGlyphs = maxp->num_glyphs; + + auto *cpal = static_cast<ots::OpenTypeCPAL*>(font->GetTypedTable(OTS_TAG_CPAL)); + if (!cpal) { + return OTS_FAILURE_MSG("Required cpal table missing"); + } + state.numPaletteEntries = cpal->num_palette_entries; + + if (numBaseGlyphRecords) { + if (offsetBaseGlyphRecords < headerSize || offsetBaseGlyphRecords >= length) { + return Error("Bad base glyph records offset in table header"); + } + if (!ParseBaseGlyphRecords(font, data + offsetBaseGlyphRecords, length - offsetBaseGlyphRecords, + numBaseGlyphRecords, numLayerRecords, state)) { + return Error("Failed to parse base glyph records"); + } + } + + if (numLayerRecords) { + if (offsetLayerRecords < headerSize || offsetLayerRecords >= length) { + return Error("Bad layer records offset in table header"); + } + if (!ParseLayerRecords(font, data + offsetLayerRecords, length - offsetLayerRecords, numLayerRecords, + state)) { + return Error("Failed to parse layer records"); + } + } + + if (offsetLayerList) { + if (offsetLayerList < headerSize || offsetLayerList >= length) { + return Error("Bad layer list offset in table header"); + } + // This reads the layer list into our state.layerList vector, but does not parse the + // paint graphs within it; these will be checked when visited via PaintColrLayers. + if (!ParseLayerList(font, data + offsetLayerList, length - offsetLayerList, state)) { + return Error("Failed to parse layer list"); + } + } + + if (offsetBaseGlyphList) { + if (offsetBaseGlyphList < headerSize || offsetBaseGlyphList >= length) { + return Error("Bad base glyph list offset in table header"); + } + // Here, we recursively check the paint graph starting at each base glyph record. + if (!ParseBaseGlyphList(font, data + offsetBaseGlyphList, length - offsetBaseGlyphList, + state)) { + return Error("Failed to parse base glyph list"); + } + } + + if (offsetClipList) { + if (offsetClipList < headerSize || offsetClipList >= length) { + return Error("Bad clip list offset in table header"); + } + if (!ParseClipList(font, data + offsetClipList, length - offsetClipList, state)) { + return Error("Failed to parse clip list"); + } + } + + if (offsetVarIdxMap) { + if (offsetVarIdxMap < headerSize || offsetVarIdxMap >= length) { + return Error("Bad delta set index offset in table header"); + } + if (!ParseDeltaSetIndexMap(font, data + offsetVarIdxMap, length - offsetVarIdxMap)) { + return Error("Failed to parse delta set index map"); + } + } + + if (offsetItemVariationStore) { + if (offsetItemVariationStore < headerSize || offsetItemVariationStore >= length) { + return Error("Bad item variation store offset in table header"); + } + if (!ParseItemVariationStore(font, data + offsetItemVariationStore, length - offsetItemVariationStore)) { + return Error("Failed to parse item variation store"); + } + } + + this->m_data = data; + this->m_length = length; + return true; +} + +bool OpenTypeCOLR::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write COLR table"); + } + + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/colr.h b/gfx/ots/src/colr.h new file mode 100644 index 0000000000..f72981193f --- /dev/null +++ b/gfx/ots/src/colr.h @@ -0,0 +1,31 @@ +// Copyright (c) 2022 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_COLR_H_ +#define OTS_COLR_H_ + +#include "ots.h" + +namespace ots { + +class OpenTypeCOLR : public Table { + public: + explicit OpenTypeCOLR(Font *font, uint32_t tag) + : Table(font, tag, 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 + +#endif // OTS_COLR_H_ + diff --git a/gfx/ots/src/cpal.cc b/gfx/ots/src/cpal.cc new file mode 100644 index 0000000000..b20088900c --- /dev/null +++ b/gfx/ots/src/cpal.cc @@ -0,0 +1,285 @@ +// Copyright (c) 2022 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 "cpal.h" +#include "name.h" + +// CPAL - Color Palette Table +// http://www.microsoft.com/typography/otspec/cpal.htm + +#define TABLE_NAME "CPAL" + +namespace { + +// Caller has sized the colorRecords array, so we know how much to try and read. +bool ParseColorRecordsArray(const ots::Font* font, + const uint8_t* data, size_t length, + std::vector<uint32_t>* colorRecords) +{ + ots::Buffer subtable(data, length); + + for (auto& color : *colorRecords) { + if (!subtable.ReadU32(&color)) { + return OTS_FAILURE_MSG("Failed to read color record"); + } + } + + return true; +} + +// Caller has sized the paletteTypes array, so we know how much to try and read. +bool ParsePaletteTypesArray(const ots::Font* font, + const uint8_t* data, size_t length, + std::vector<uint32_t>* paletteTypes) +{ + ots::Buffer subtable(data, length); + + constexpr uint32_t USABLE_WITH_LIGHT_BACKGROUND = 0x0001; + constexpr uint32_t USABLE_WITH_DARK_BACKGROUND = 0x0002; + constexpr uint32_t RESERVED = ~(USABLE_WITH_LIGHT_BACKGROUND | USABLE_WITH_DARK_BACKGROUND); + + for (auto& type : *paletteTypes) { + if (!subtable.ReadU32(&type)) { + return OTS_FAILURE_MSG("Failed to read palette type"); + } + if (type & RESERVED) { + // Should we treat this as failure? For now, just a warning; seems unlikely + // to be dangerous. + OTS_WARNING("Invalid (reserved) palette type flags %08x", type); + type &= ~RESERVED; + } + } + + return true; +} + +// Caller has sized the labels array, so we know how much to try and read. +bool ParseLabelsArray(const ots::Font* font, + const uint8_t* data, size_t length, + std::vector<uint16_t>* labels, + const char* labelType) +{ + ots::Buffer subtable(data, length); + + auto* name = static_cast<ots::OpenTypeNAME*>(font->GetTypedTable(OTS_TAG_NAME)); + if (!name) { + return OTS_FAILURE_MSG("Required name table missing"); + } + + for (auto& nameID : *labels) { + if (!subtable.ReadU16(&nameID)) { + return OTS_FAILURE_MSG("Failed to read %s label ID", labelType); + } + if (nameID != 0xffff) { + if (!name->IsValidNameId(nameID)) { + OTS_WARNING("Label ID %u for %s missing from name table", nameID, labelType); + nameID = 0xffff; + } + } + } + + return true; +} + +} // namespace + +namespace ots { + +bool OpenTypeCPAL::Parse(const uint8_t *data, size_t length) { + Font *font = GetFont(); + Buffer table(data, length); + + // Header fields common to versions 0 and 1. These are recomputed + // from the array sizes during serialization. + uint16_t numPalettes; + uint16_t numColorRecords; + uint32_t colorRecordsArrayOffset; + + if (!table.ReadU16(&this->version) || + !table.ReadU16(&this->num_palette_entries) || + !table.ReadU16(&numPalettes) || + !table.ReadU16(&numColorRecords) || + !table.ReadU32(&colorRecordsArrayOffset)) { + return Error("Failed to read CPAL table header"); + } + + if (this->version > 1) { + return Error("Unknown CPAL table version %u", this->version); + } + + if (!this->num_palette_entries || !numPalettes || !numColorRecords) { + return Error("Empty CPAL is not valid"); + } + + if (this->num_palette_entries > numColorRecords) { + return Error("Not enough color records for a complete palette"); + } + + uint32_t headerSize = 4 * sizeof(uint16_t) + sizeof(uint32_t) + + numPalettes * sizeof(uint16_t); + + // uint16_t colorRecordIndices[numPalettes] + this->colorRecordIndices.resize(numPalettes); + for (auto& colorRecordIndex : this->colorRecordIndices) { + if (!table.ReadU16(&colorRecordIndex)) { + return Error("Failed to read color record index"); + } + if (colorRecordIndex > numColorRecords - this->num_palette_entries) { + return Error("Palette overflows color records array"); + } + } + + uint32_t paletteTypesArrayOffset = 0; + uint32_t paletteLabelsArrayOffset = 0; + uint32_t paletteEntryLabelsArrayOffset = 0; + if (this->version == 1) { + if (!table.ReadU32(&paletteTypesArrayOffset) || + !table.ReadU32(&paletteLabelsArrayOffset) || + !table.ReadU32(&paletteEntryLabelsArrayOffset)) { + return Error("Failed to read CPAL v.1 table header"); + } + headerSize += 3 * sizeof(uint32_t); + } + + // The following arrays may occur in any order, as they're independently referenced + // by offsets in the header. + + if (colorRecordsArrayOffset < headerSize || colorRecordsArrayOffset >= length) { + return Error("Bad color records array offset in table header"); + } + this->colorRecords.resize(numColorRecords); + if (!ParseColorRecordsArray(font, data + colorRecordsArrayOffset, length - colorRecordsArrayOffset, + &this->colorRecords)) { + return Error("Failed to parse color records array"); + } + + if (paletteTypesArrayOffset) { + if (paletteTypesArrayOffset < headerSize || paletteTypesArrayOffset >= length) { + return Error("Bad palette types array offset in table header"); + } + this->paletteTypes.resize(numPalettes); + if (!ParsePaletteTypesArray(font, data + paletteTypesArrayOffset, length - paletteTypesArrayOffset, + &this->paletteTypes)) { + return Error("Failed to parse palette types array"); + } + } + + if (paletteLabelsArrayOffset) { + if (paletteLabelsArrayOffset < headerSize || paletteLabelsArrayOffset >= length) { + return Error("Bad palette labels array offset in table header"); + } + this->paletteLabels.resize(numPalettes); + if (!ParseLabelsArray(font, data + paletteLabelsArrayOffset, length - paletteLabelsArrayOffset, + &this->paletteLabels, "palette")) { + return Error("Failed to parse palette labels array"); + } + } + + if (paletteEntryLabelsArrayOffset) { + if (paletteEntryLabelsArrayOffset < headerSize || paletteEntryLabelsArrayOffset >= length) { + return Error("Bad palette entry labels array offset in table header"); + } + this->paletteEntryLabels.resize(this->num_palette_entries); + if (!ParseLabelsArray(font, data + paletteEntryLabelsArrayOffset, length - paletteEntryLabelsArrayOffset, + &this->paletteEntryLabels, "palette entry")) { + return Error("Failed to parse palette entry labels array"); + } + } + + return true; +} + +bool OpenTypeCPAL::Serialize(OTSStream *out) { + uint16_t numPalettes = this->colorRecordIndices.size(); + uint16_t numColorRecords = this->colorRecords.size(); + +#ifndef NDEBUG + off_t start = out->Tell(); +#endif + + size_t colorRecordsArrayOffset = 4 * sizeof(uint16_t) + sizeof(uint32_t) + + numPalettes * sizeof(uint16_t); + if (this->version == 1) { + colorRecordsArrayOffset += 3 * sizeof(uint32_t); + } + + size_t totalLen = colorRecordsArrayOffset + numColorRecords * sizeof(uint32_t); + + if (!out->WriteU16(this->version) || + !out->WriteU16(this->num_palette_entries) || + !out->WriteU16(numPalettes) || + !out->WriteU16(numColorRecords) || + !out->WriteU32(colorRecordsArrayOffset)) { + return Error("Failed to write CPAL header"); + } + + for (auto i : this->colorRecordIndices) { + if (!out->WriteU16(i)) { + return Error("Failed to write color record indices"); + } + } + + if (this->version == 1) { + size_t paletteTypesArrayOffset = 0; + if (!this->paletteTypes.empty()) { + assert(paletteTypes.size() == numPalettes); + paletteTypesArrayOffset = totalLen; + totalLen += numPalettes * sizeof(uint32_t); + } + + size_t paletteLabelsArrayOffset = 0; + if (!this->paletteLabels.empty()) { + assert(paletteLabels.size() == numPalettes); + paletteLabelsArrayOffset = totalLen; + totalLen += numPalettes * sizeof(uint16_t); + } + + size_t paletteEntryLabelsArrayOffset = 0; + if (!this->paletteEntryLabels.empty()) { + assert(paletteEntryLabels.size() == this->num_palette_entries); + paletteEntryLabelsArrayOffset = totalLen; + totalLen += this->num_palette_entries * sizeof(uint16_t); + } + + if (!out->WriteU32(paletteTypesArrayOffset) || + !out->WriteU32(paletteLabelsArrayOffset) || + !out->WriteU32(paletteEntryLabelsArrayOffset)) { + return Error("Failed to write CPAL v.1 header"); + } + } + + for (auto i : this->colorRecords) { + if (!out->WriteU32(i)) { + return Error("Failed to write color records"); + } + } + + if (this->version == 1) { + for (auto i : this->paletteTypes) { + if (!out->WriteU32(i)) { + return Error("Failed to write palette types"); + } + } + + for (auto i : this->paletteLabels) { + if (!out->WriteU16(i)) { + return Error("Failed to write palette labels"); + } + } + + for (auto i : this->paletteEntryLabels) { + if (!out->WriteU16(i)) { + return Error("Failed to write palette entry labels"); + } + } + } + + assert(size_t(out->Tell() - start) == totalLen); + + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/cpal.h b/gfx/ots/src/cpal.h new file mode 100644 index 0000000000..8ce29c024e --- /dev/null +++ b/gfx/ots/src/cpal.h @@ -0,0 +1,40 @@ +// Copyright (c) 2022 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_CPAL_H_ +#define OTS_CPAL_H_ + +#include "ots.h" + +#include <vector> + +namespace ots { + +class OpenTypeCPAL : public Table { + public: + explicit OpenTypeCPAL(Font *font, uint32_t tag) + : Table(font, tag, tag) { + } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + // This is public so that COLR can access it. + uint16_t num_palette_entries; + + private: + uint16_t version; + + std::vector<uint16_t> colorRecordIndices; + std::vector<uint32_t> colorRecords; + + // Arrays present only if version == 1. + std::vector<uint32_t> paletteTypes; + std::vector<uint16_t> paletteLabels; + std::vector<uint16_t> paletteEntryLabels; +}; + +} // namespace ots + +#endif // OTS_CPAL_H_ diff --git a/gfx/ots/src/cvar.cc b/gfx/ots/src/cvar.cc new file mode 100644 index 0000000000..a2bad7a15e --- /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<OpenTypeFVAR*>( + 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 0000000000..8f31e98cd1 --- /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 new file mode 100644 index 0000000000..2e0257889a --- /dev/null +++ b/gfx/ots/src/cvt.cc @@ -0,0 +1,46 @@ +// 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 "cvt.h" + +// cvt - Control Value Table +// http://www.microsoft.com/typography/otspec/cvt.htm + +namespace ots { + +bool OpenTypeCVT::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (length >= 128 * 1024u) { + return Error("Length (%d) > 120K"); // almost all cvt tables are less than 4k bytes. + } + + if (length % 2 != 0) { + return Error("Uneven table length (%d)", length); + } + + if (!table.Skip(length)) { + return Error("Table length too high"); + } + + this->data = data; + this->length = length; + return true; +} + +bool OpenTypeCVT::Serialize(OTSStream *out) { + if (!out->Write(this->data, this->length)) { + return Error("Failed to write cvt table"); + } + + return true; +} + +bool OpenTypeCVT::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; +} + +} // namespace ots diff --git a/gfx/ots/src/cvt.h b/gfx/ots/src/cvt.h new file mode 100644 index 0000000000..88a96ca203 --- /dev/null +++ b/gfx/ots/src/cvt.h @@ -0,0 +1,28 @@ +// 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_CVT_H_ +#define OTS_CVT_H_ + +#include "ots.h" + +namespace ots { + +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; +}; + +} // namespace ots + +#endif // OTS_CVT_H_ diff --git a/gfx/ots/src/feat.cc b/gfx/ots/src/feat.cc new file mode 100644 index 0000000000..19f22c3bc5 --- /dev/null +++ b/gfx/ots/src/feat.cc @@ -0,0 +1,190 @@ +// 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) { + 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<size_t> 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<OpenTypeNAME*>( + 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<uint16_t>(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<OpenTypeNAME*>( + 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 0000000000..9a08ac4170 --- /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 <vector> +#include <unordered_set> + +#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<OpenTypeFEAT> { + explicit FeatureDefn(OpenTypeFEAT* parent) + : TablePart<OpenTypeFEAT>(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 = 0x3700; + static const uint16_t DEFAULT_SETTING = 0x00FF; + uint16_t label; + }; + struct FeatureSettingDefn : public TablePart<OpenTypeFEAT> { + explicit FeatureSettingDefn(OpenTypeFEAT* parent) + : TablePart<OpenTypeFEAT>(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<FeatureDefn> features; + std::vector<FeatureSettingDefn> featSettings; + std::unordered_set<uint32_t> feature_ids; +}; + +} // namespace ots + +#endif // OTS_FEAT_H_ diff --git a/gfx/ots/src/fpgm.cc b/gfx/ots/src/fpgm.cc new file mode 100644 index 0000000000..bb52b367fb --- /dev/null +++ b/gfx/ots/src/fpgm.cc @@ -0,0 +1,42 @@ +// 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 "fpgm.h" + +// fpgm - Font Program +// http://www.microsoft.com/typography/otspec/fpgm.htm + +namespace ots { + +bool OpenTypeFPGM::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (length >= 128 * 1024u) { + return Error("length (%ld) > 120", length); // almost all fpgm tables are less than 5k bytes. + } + + if (!table.Skip(length)) { + return Error("Bad table length"); + } + + this->data = data; + this->length = length; + return true; +} + +bool OpenTypeFPGM::Serialize(OTSStream *out) { + if (!out->Write(this->data, this->length)) { + return Error("Failed to write fpgm table"); + } + + return true; +} + +bool OpenTypeFPGM::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; +} + +} // namespace ots diff --git a/gfx/ots/src/fpgm.h b/gfx/ots/src/fpgm.h new file mode 100644 index 0000000000..9ed6b34bf2 --- /dev/null +++ b/gfx/ots/src/fpgm.h @@ -0,0 +1,28 @@ +// 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_FPGM_H_ +#define OTS_FPGM_H_ + +#include "ots.h" + +namespace ots { + +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; +}; + +} // namespace ots + +#endif // OTS_FPGM_H_ diff --git a/gfx/ots/src/fvar.cc b/gfx/ots/src/fvar.cc new file mode 100644 index 0000000000..6f9b4d6ebf --- /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 0000000000..a469c8cddf --- /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 <vector> + +#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<VariationAxisRecord> axes; + + struct InstanceRecord { + uint16_t subfamilyNameID; + uint16_t flags; + std::vector<Fixed> coordinates; + uint16_t postScriptNameID; // optional + }; + std::vector<InstanceRecord> instances; + + bool instancesHavePostScriptNameID; +}; + +} // namespace ots + +#endif // OTS_FVAR_H_ diff --git a/gfx/ots/src/gasp.cc b/gfx/ots/src/gasp.cc new file mode 100644 index 0000000000..2a03c831ff --- /dev/null +++ b/gfx/ots/src/gasp.cc @@ -0,0 +1,84 @@ +// 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 "gasp.h" + +// gasp - Grid-fitting And Scan-conversion Procedure +// http://www.microsoft.com/typography/otspec/gasp.htm + +namespace ots { + +bool OpenTypeGASP::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + uint16_t num_ranges = 0; + if (!table.ReadU16(&this->version) || + !table.ReadU16(&num_ranges)) { + return Error("Failed to read table header"); + } + + if (this->version > 1) { + // Lots of Linux fonts have bad version numbers... + return Drop("Unsupported version: %u", this->version); + } + + if (num_ranges == 0) { + return Drop("numRanges is zero"); + } + + 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 Error("Failed to read GASPRANGE %d", i); + } + 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. + return Drop("Ranges are not sorted"); + } + if ((i == num_ranges - 1u) && // never underflow. + (max_ppem != 0xffffu)) { + return Drop("The last record should be 0xFFFF as a sentinel value " + "for rangeMaxPPEM"); + } + + if (behavior >> 8) { + Warning("Undefined bits are used: %x", behavior); + // mask undefined bits. + behavior &= 0x000fu; + } + + if (this->version == 0 && (behavior >> 2) != 0) { + Warning("Changed the version number to 1"); + this->version = 1; + } + + this->gasp_ranges.push_back(std::make_pair(max_ppem, behavior)); + } + + return true; +} + +bool OpenTypeGASP::Serialize(OTSStream *out) { + const uint16_t num_ranges = static_cast<uint16_t>(this->gasp_ranges.size()); + if (num_ranges != this->gasp_ranges.size() || + !out->WriteU16(this->version) || + !out->WriteU16(num_ranges)) { + return Error("Failed to write table header"); + } + + for (uint16_t i = 0; i < num_ranges; ++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; +} + +} // namespace ots diff --git a/gfx/ots/src/gasp.h b/gfx/ots/src/gasp.h new file mode 100644 index 0000000000..ce9e987aad --- /dev/null +++ b/gfx/ots/src/gasp.h @@ -0,0 +1,32 @@ +// 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_GASP_H_ +#define OTS_GASP_H_ + +#include <new> +#include <utility> +#include <vector> + +#include "ots.h" + +namespace ots { + +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<std::pair<uint16_t, uint16_t> > gasp_ranges; +}; + +} // namespace ots + +#endif // OTS_GASP_H_ diff --git a/gfx/ots/src/gdef.cc b/gfx/ots/src/gdef.cc new file mode 100644 index 0000000000..0e01a93845 --- /dev/null +++ b/gfx/ots/src/gdef.cc @@ -0,0 +1,364 @@ +// 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 "gdef.h" + +#include <limits> +#include <vector> + +#include "gpos.h" +#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 + +namespace { + +// The maximum class value in the glyph class definision table. +const uint16_t kMaxGlyphClassDefValue = 4; +// The maximum format number of caret value tables. +const uint16_t kMaxCaretValueFormat = 3; + +} // namespace + +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 Error("Failed to read gdef header"); + } + const unsigned attach_points_end = + 2 * static_cast<unsigned>(glyph_count) + 4; + if (attach_points_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad glyph count in gdef"); + } + if (offset_coverage == 0 || offset_coverage >= length || + offset_coverage < attach_points_end) { + return Error("Bad coverage offset %d", offset_coverage); + } + if (glyph_count > this->m_num_glyphs) { + return Error("Bad glyph count %u", glyph_count); + } + + std::vector<uint16_t> attach_points; + attach_points.resize(glyph_count); + for (unsigned i = 0; i < glyph_count; ++i) { + if (!subtable.ReadU16(&attach_points[i])) { + return Error("Can't read attachment point %d", i); + } + if (attach_points[i] >= length || + attach_points[i] < attach_points_end) { + return Error("Bad attachment point %d of %d", i, attach_points[i]); + } + } + + // Parse 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 + for (unsigned i = 0; i < attach_points.size(); ++i) { + subtable.set_offset(attach_points[i]); + uint16_t point_count = 0; + if (!subtable.ReadU16(&point_count)) { + return Error("Can't read point count %d", i); + } + if (point_count == 0) { + 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 Error("Can't read point index %d in point %d", j, i); + } + // Contour point indices are in increasing numerical order + if (last_point_index != 0 && last_point_index >= point_index) { + return Error("bad contour indices: %u >= %u", + last_point_index, point_index); + } + last_point_index = point_index; + } + } + return true; +} + +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 Error("Can't read caret structure"); + } + const unsigned lig_glyphs_end = + 2 * static_cast<unsigned>(lig_glyph_count) + 4; + if (lig_glyphs_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad caret structure"); + } + if (offset_coverage == 0 || offset_coverage >= length || + offset_coverage < lig_glyphs_end) { + return Error("Bad caret coverate offset %d", offset_coverage); + } + if (lig_glyph_count > this->m_num_glyphs) { + return Error("bad ligature glyph count: %u", lig_glyph_count); + } + + std::vector<uint16_t> lig_glyphs; + lig_glyphs.resize(lig_glyph_count); + for (unsigned i = 0; i < lig_glyph_count; ++i) { + if (!subtable.ReadU16(&lig_glyphs[i])) { + return Error("Can't read ligature glyph location %d", i); + } + if (lig_glyphs[i] >= length || lig_glyphs[i] < lig_glyphs_end) { + return Error("Bad ligature glyph location %d in glyph %d", lig_glyphs[i], i); + } + } + + // Parse 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 + for (unsigned i = 0; i < lig_glyphs.size(); ++i) { + subtable.set_offset(lig_glyphs[i]); + uint16_t caret_count = 0; + if (!subtable.ReadU16(&caret_count)) { + return Error("Can't read caret count for glyph %d", i); + } + if (caret_count == 0) { + return Error("bad caret value count: %u", caret_count); + } + + std::vector<uint16_t> caret_value_offsets; + caret_value_offsets.resize(caret_count); + unsigned caret_value_offsets_end = 2 * static_cast<unsigned>(caret_count) + 2; + for (unsigned j = 0; j < caret_count; ++j) { + if (!subtable.ReadU16(&caret_value_offsets[j])) { + return 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 Error("Bad caret offset %d for caret %d glyph %d", caret_value_offsets[j], j, i); + } + } + + // Parse caret values table + for (unsigned j = 0; j < caret_count; ++j) { + subtable.set_offset(lig_glyphs[i] + caret_value_offsets[j]); + uint16_t caret_format = 0; + if (!subtable.ReadU16(&caret_format)) { + return Error("Can't read caret values table %d in glyph %d", j, i); + } + if (caret_format == 0 || caret_format > kMaxCaretValueFormat) { + 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 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 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 Error("Can' read mark glyph table structure"); + } + if (format != 1) { + return Error("bad mark glyph set table format: %u", format); + } + + const unsigned mark_sets_end = 2 * static_cast<unsigned>(mark_set_count) + 4; + if (mark_sets_end > std::numeric_limits<uint16_t>::max()) { + return 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 Error("Can't read covrage location for mark set %d", i); + } + if (offset_coverage >= length || + offset_coverage < mark_sets_end) { + return Error("Bad coverage location %d for mark set %d", offset_coverage, 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); + } + } + this->num_mark_glyph_sets = mark_set_count; + return true; +} + +bool OpenTypeGDEF::Parse(const uint8_t *data, size_t length) { + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + + // Grab the number of glyphs in the font from the maxp table to check + // GlyphIDs in GDEF table. + if (!maxp) { + return Error("No maxp table in font, needed by GDEF"); + } + this->m_num_glyphs = maxp->num_glyphs; + + Buffer table(data, length); + + uint16_t version_major = 0, version_minor = 0; + if (!table.ReadU16(&version_major) || + !table.ReadU16(&version_minor)) { + return Error("Incomplete table"); + } + if (version_major != 1 || version_minor == 1) { // there is no v1.1 + return Error("Bad version"); + } + + uint16_t offset_glyph_class_def = 0; + uint16_t offset_attach_list = 0; + uint16_t offset_lig_caret_list = 0; + uint16_t offset_mark_attach_class_def = 0; + if (!table.ReadU16(&offset_glyph_class_def) || + !table.ReadU16(&offset_attach_list) || + !table.ReadU16(&offset_lig_caret_list) || + !table.ReadU16(&offset_mark_attach_class_def)) { + return Error("Incomplete table"); + } + uint16_t offset_mark_glyph_sets_def = 0; + if (version_minor >= 2) { + if (!table.ReadU16(&offset_mark_glyph_sets_def)) { + 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 (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 Error("Invalid offset to glyph classes"); + } + if (!ots::ParseClassDefTable(GetFont(), data + offset_glyph_class_def, + length - offset_glyph_class_def, + this->m_num_glyphs, kMaxGlyphClassDefValue)) { + return Error("Invalid glyph classes"); + } + } + + if (offset_attach_list) { + if (offset_attach_list >= length || + offset_attach_list < gdef_header_end) { + return Error("Invalid offset to 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 Error("Invalid offset to 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 Error("Invalid offset to 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"); + } + } + + if (offset_mark_glyph_sets_def) { + if (offset_mark_glyph_sets_def >= length || + offset_mark_glyph_sets_def < gdef_header_end) { + return Error("invalid offset to mark glyph sets"); + } + if (!ParseMarkGlyphSetsDefTable(data + offset_mark_glyph_sets_def, + length - offset_mark_glyph_sets_def)) { + return Error("Invalid mark glyph sets"); + } + } + + 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; +} + +bool OpenTypeGDEF::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/gdef.h b/gfx/ots/src/gdef.h new file mode 100644 index 0000000000..7c7cc0ce53 --- /dev/null +++ b/gfx/ots/src/gdef.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef OTS_GDEF_H_ +#define OTS_GDEF_H_ + +#include "ots.h" + +namespace ots { + +class OpenTypeGDEF : public Table { + public: + explicit OpenTypeGDEF(Font *font, uint32_t tag) + : Table(font, tag, tag), + num_mark_glyph_sets(0), + m_data(NULL), + m_length(0), + m_num_glyphs(0) { + } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + uint16_t num_mark_glyph_sets; + + 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 + +#endif + diff --git a/gfx/ots/src/glat.cc b/gfx/ots/src/glat.cc new file mode 100644 index 0000000000..b42453008d --- /dev/null +++ b/gfx/ots/src/glat.cc @@ -0,0 +1,458 @@ +// 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 <list> +#include <memory> + +namespace ots { + +// ----------------------------------------------------------------------------- +// OpenTypeGLAT_v1 +// ----------------------------------------------------------------------------- + +bool OpenTypeGLAT_v1::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + OpenTypeGLOC* gloc = static_cast<OpenTypeGLOC*>( + 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<uint32_t>& locations = gloc->GetLocations(); + if (locations.empty()) { + return DropGraphite("No locations from Gloc table"); + } + std::list<uint32_t> 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<OpenTypeGLOC*>( + 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<uint32_t>& locations = gloc->GetLocations(); + if (locations.empty()) { + return DropGraphite("No locations from Gloc table"); + } + std::list<uint32_t> 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<OpenTypeGLOC*>( + 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 <= OTS_MAX_DECOMPRESSED_TABLE_SIZE + if (decompressed_size > OTS_MAX_DECOMPRESSED_TABLE_SIZE) { + return DropGraphite("Decompressed size exceeds %gMB: %gMB", + OTS_MAX_DECOMPRESSED_TABLE_SIZE / (1024.0 * 1024.0), + decompressed_size / (1024.0 * 1024.0)); + } + std::unique_ptr<uint8_t> decompressed(new uint8_t[decompressed_size]()); + size_t outputSize = 0; + bool ret = mozilla::Compression::LZ4::decompressPartial( + reinterpret_cast<const char*>(data + table.offset()), + table.remaining(), // input buffer size (input size + padding) + reinterpret_cast<char*>(decompressed.get()), + decompressed_size, // target output size + &outputSize); // return output size + if (!ret || outputSize != decompressed_size) { + return DropGraphite("Decompression failed"); + } + return this->Parse(decompressed.get(), decompressed_size, true); + } + default: + return DropGraphite("Unknown compression scheme"); + } + if (this->compHead & RESERVED) { + Warning("Nonzero reserved"); + } + + const std::vector<uint32_t>& locations = gloc->GetLocations(); + if (locations.empty()) { + return DropGraphite("No locations from Gloc table"); + } + std::list<uint32_t> 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) { + 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 0000000000..f06ca16138 --- /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 <vector> + +#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<OpenTypeGLAT_v1> { + explicit GlatEntry(OpenTypeGLAT_v1* parent) + : TablePart<OpenTypeGLAT_v1>(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + uint8_t attNum; + uint8_t num; + std::vector<int16_t> attributes; + }; + uint32_t version; + std::vector<GlatEntry> 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<OpenTypeGLAT_v2> { + explicit GlatEntry(OpenTypeGLAT_v2* parent) + : TablePart<OpenTypeGLAT_v2>(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + int16_t attNum; + int16_t num; + std::vector<int16_t> attributes; + }; + uint32_t version; + std::vector<GlatEntry> 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<OpenTypeGLAT_v3> { + explicit GlyphAttrs(OpenTypeGLAT_v3* parent) + : TablePart<OpenTypeGLAT_v3>(parent), octabox(parent) { } + bool ParsePart(Buffer& table OTS_UNUSED) { return false; } + bool ParsePart(Buffer& table, const size_t size); + bool SerializePart(OTSStream* out) const; + struct OctaboxMetrics : public TablePart<OpenTypeGLAT_v3> { + explicit OctaboxMetrics(OpenTypeGLAT_v3* parent) + : TablePart<OpenTypeGLAT_v3>(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + struct SubboxEntry : public TablePart<OpenTypeGLAT_v3> { + explicit SubboxEntry(OpenTypeGLAT_v3* parent) + : TablePart<OpenTypeGLAT_v3>(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<SubboxEntry> subboxes; + }; + struct GlatEntry : public TablePart<OpenTypeGLAT_v3> { + explicit GlatEntry(OpenTypeGLAT_v3* parent) + : TablePart<OpenTypeGLAT_v3>(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + int16_t attNum; + int16_t num; + std::vector<int16_t> attributes; + }; + OctaboxMetrics octabox; + std::vector<GlatEntry> 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<GlyphAttrs> 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 0000000000..396e14a13d --- /dev/null +++ b/gfx/ots/src/gloc.cc @@ -0,0 +1,105 @@ +// 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) { + Buffer table(data, length); + OpenTypeNAME* name = static_cast<OpenTypeNAME*>( + 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 calculate 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<uint32_t>(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<uint16_t>(location))) { + return false; + } + } + return true; + }()) || + (this->flags & ATTRIB_IDS && !SerializeParts(this->attribIds, out))) { + return Error("Failed to write table"); + } + return true; +} + +const std::vector<uint32_t>& 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 0000000000..60184ffb92 --- /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 <vector> + +#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<uint32_t>& 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<uint32_t> locations; + std::vector<uint16_t> attribIds; +}; + +} // namespace ots + +#endif // OTS_GLOC_H_ diff --git a/gfx/ots/src/glyf.cc b/gfx/ots/src/glyf.cc new file mode 100644 index 0000000000..31487957bf --- /dev/null +++ b/gfx/ots/src/glyf.cc @@ -0,0 +1,632 @@ +// 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 "glyf.h" + +#include <algorithm> +#include <limits> + +#include "head.h" +#include "loca.h" +#include "maxp.h" +#include "name.h" + +// glyf - Glyph Data +// http://www.microsoft.com/typography/otspec/glyf.htm + +namespace ots { + +bool OpenTypeGLYF::ParseFlagsForSimpleGlyph(Buffer &glyph, + uint32_t num_flags, + std::vector<uint8_t>& flags, + uint32_t *flag_index, + uint32_t *coordinates_length) { + uint8_t flag = 0; + if (!glyph.ReadU8(&flag)) { + return Error("Can't read flag"); + } + + uint32_t delta = 0; + if (flag & (1u << 1)) { // x-Short + ++delta; + } else if (!(flag & (1u << 4))) { + delta += 2; + } + + if (flag & (1u << 2)) { // y-Short + ++delta; + } else if (!(flag & (1u << 5))) { + delta += 2; + } + + /* 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); + } + + flags[*flag_index] = flag & ~(1u << 3); + + if (flag & (1u << 3)) { // repeat + if (*flag_index + 1 >= num_flags) { + return Error("Count too high (%d + 1 >= %d)", *flag_index, num_flags); + } + uint8_t repeat = 0; + if (!glyph.ReadU8(&repeat)) { + return Error("Can't read repeat value"); + } + if (repeat == 0) { + return Error("Zero repeat"); + } + delta += (delta * repeat); + + if (*flag_index + repeat >= num_flags) { + return Error("Count too high (%d >= %d)", *flag_index + repeat, num_flags); + } + + while (repeat--) { + flags[++*flag_index] = flag & ~(1u << 3); + } + } + + if (flag & (1u << 7)) { // reserved flag + return Error("Bad glyph flag (%d), reserved bit 7 must be set to zero", flag); + } + + *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; +} + +#define X_SHORT_VECTOR (1u << 1) +#define Y_SHORT_VECTOR (1u << 2) +#define X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR (1u << 4) +#define Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR (1u << 5) + +bool OpenTypeGLYF::ParseSimpleGlyph(Buffer &glyph, + unsigned gid, + int16_t num_contours, + int16_t& xmin, + int16_t& ymin, + int16_t& xmax, + int16_t& ymax, + bool is_tricky_font) { + // read the end-points array + uint16_t num_flags = 0; + for (int i = 0; i < num_contours; ++i) { + uint16_t tmp_index = 0; + if (!glyph.ReadU16(&tmp_index)) { + return Error("Can't read contour index %d (glyph %u)", i, gid); + } + if (tmp_index == 0xffffu) { + return Error("Bad contour index %d (glyph %u)", i, gid); + } + // check if the indices are monotonically increasing + if (i && (tmp_index + 1 <= num_flags)) { + return Error("Decreasing contour index %d + 1 <= %d (glyph %u)", tmp_index, num_flags, gid); + } + num_flags = tmp_index + 1; + } + + if (this->maxp->version_1 && + num_flags > this->maxp->max_points) { + Warning("Number of contour points exceeds maxp maxPoints, adjusting limit (glyph %u)", gid); + this->maxp->max_points = num_flags; + } + + uint16_t bytecode_length = 0; + if (!glyph.ReadU16(&bytecode_length)) { + return Error("Can't read bytecode length"); + } + + if (this->maxp->version_1 && + this->maxp->max_size_glyf_instructions < bytecode_length) { + Warning("Bytecode length is bigger than maxp.maxSizeOfInstructions %d: %d (glyph %u)", + this->maxp->max_size_glyf_instructions, bytecode_length, gid); + this->maxp->max_size_glyf_instructions = bytecode_length; + } + + if (!glyph.Skip(bytecode_length)) { + return Error("Can't read bytecode of length %d (glyph %u)", bytecode_length, gid); + } + + uint32_t coordinates_length = 0; + std::vector<uint8_t> flags(num_flags); + for (uint32_t i = 0; i < num_flags; ++i) { + if (!ParseFlagsForSimpleGlyph(glyph, num_flags, flags, &i, &coordinates_length)) { + return Error("Failed to parse glyph flags %d (glyph %u)", i, gid); + } + } + + bool adjusted_bbox = false; + int16_t x = 0, y = 0; + + // Read and check x-coords + for (uint32_t i = 0; i < num_flags; ++i) { + uint8_t flag = flags[i]; + if (flag & X_SHORT_VECTOR) { + uint8_t dx; + if (!glyph.ReadU8(&dx)) { + return Error("Glyph too short %d (glyph %u)", glyph.length(), gid); + } + if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) { + x += dx; + } else { + x -= dx; + } + } else if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) { + // x remains unchanged + } else { + int16_t dx; + if (!glyph.ReadS16(&dx)) { + return Error("Glyph too short %d (glyph %u)", glyph.length(), gid); + } + x += dx; + } + if (x < xmin) { + xmin = x; + adjusted_bbox = true; + } + if (x > xmax) { + xmax = x; + adjusted_bbox = true; + } + } + + // Read and check y-coords + for (uint32_t i = 0; i < num_flags; ++i) { + uint8_t flag = flags[i]; + if (flag & Y_SHORT_VECTOR) { + uint8_t dy; + if (!glyph.ReadU8(&dy)) { + return Error("Glyph too short %d (glyph %u)", glyph.length(), gid); + } + if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) { + y += dy; + } else { + y -= dy; + } + } else if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) { + // x remains unchanged + } else { + int16_t dy; + if (!glyph.ReadS16(&dy)) { + return Error("Glyph too short %d (glyph %u)", glyph.length(), gid); + } + y += dy; + } + if (y < ymin) { + ymin = y; + adjusted_bbox = true; + } + if (y > ymax) { + ymax = y; + adjusted_bbox = true; + } + } + + if (glyph.remaining() > 3) { + // We allow 0-3 bytes difference since gly_length is 4-bytes aligned, + // zero-padded length. + Warning("Extra bytes at end of the glyph: %d (glyph %u)", glyph.remaining(), gid); + } + + if (adjusted_bbox) { + if (is_tricky_font) { + Warning("Glyph bbox was incorrect; NOT adjusting tricky font (glyph %u)", gid); + } else { + Warning("Glyph bbox was incorrect; adjusting (glyph %u)", gid); + // copy the numberOfContours field + this->iov.push_back(std::make_pair(glyph.buffer(), 2)); + // output a fixed-up version of the bounding box + uint8_t* fixed_bbox = new uint8_t[8]; + fixed_bboxes.push_back(fixed_bbox); + xmin = ots_htons(xmin); + std::memcpy(fixed_bbox, &xmin, 2); + ymin = ots_htons(ymin); + std::memcpy(fixed_bbox + 2, &ymin, 2); + xmax = ots_htons(xmax); + std::memcpy(fixed_bbox + 4, &xmax, 2); + ymax = ots_htons(ymax); + std::memcpy(fixed_bbox + 6, &ymax, 2); + this->iov.push_back(std::make_pair(fixed_bbox, 8)); + // copy the remainder of the glyph data + this->iov.push_back(std::make_pair(glyph.buffer() + 10, glyph.offset() - 10)); + return true; + } + } + + this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset())); + + return true; +} + +#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, + ComponentPointCount* component_point_count) { + 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"); + } + + 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"); + } + } + + // Push inital components on stack at level 1 + // to traverse them in parent function. + component_point_count->gid_stack.push_back({gid, 1}); + } 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"); + } + + if (this->maxp->version_1 && + 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); + this->maxp->max_size_glyf_instructions = bytecode_length; + } + + if (!glyph.Skip(bytecode_length)) { + return Error("Can't read bytecode of length %d", bytecode_length); + } + } + + this->iov.push_back(std::make_pair(glyph.buffer(), glyph.offset())); + + return true; +} + +bool OpenTypeGLYF::Parse(const uint8_t *data, size_t length) { + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + OpenTypeLOCA *loca = static_cast<OpenTypeLOCA*>( + GetFont()->GetTypedTable(OTS_TAG_LOCA)); + OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); + if (!maxp || !loca || !head) { + return Error("Missing maxp or loca or head table needed by glyf table"); + } + + OpenTypeNAME *name = static_cast<OpenTypeNAME*>( + GetFont()->GetTypedTable(OTS_TAG_NAME)); + bool is_tricky = name->IsTrickyFont(); + + this->maxp = maxp; + + const unsigned num_glyphs = maxp->num_glyphs; + std::vector<uint32_t> &offsets = loca->offsets; + + if (offsets.size() != num_glyphs + 1) { + return Error("Invalid glyph offsets size %ld != %d", offsets.size(), num_glyphs + 1); + } + + std::vector<uint32_t> resulting_offsets(num_glyphs + 1); + uint32_t current_offset = 0; + + for (unsigned i = 0; i < num_glyphs; ++i) { + + Buffer glyph(GetGlyphBufferSection(data, length, offsets, i)); + if (!glyph.buffer()) + return false; + + if (!glyph.length()) { + resulting_offsets[i] = current_offset; + continue; + } + + int16_t num_contours, xmin, ymin, xmax, ymax; + 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 Error("Bad number of contours %d in glyph %d", num_contours, i); + } + + // workaround for fonts in http://www.princexml.com/fonts/ + if ((xmin == 32767) && + (xmax == -32767) && + (ymin == 32767) && + (ymax == -32767)) { + Warning("bad xmin/xmax/ymin/ymax values"); + xmin = xmax = ymin = ymax = 0; + } + + if (xmin > xmax || ymin > ymax) { + return Error("Bad bounding box values bl=(%d, %d), tr=(%d, %d) in glyph %d", xmin, ymin, xmax, ymax, 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, i, num_contours, xmin, ymin, xmax, ymax, is_tricky)) { + return Error("Failed to parse glyph %d", i); + } + } else { + + ComponentPointCount component_point_count; + if (!ParseCompositeGlyph(glyph, &component_point_count)) { + return Error("Failed to parse glyph %d", i); + } + + // Check maxComponentDepth and validate maxComponentPoints. + // ParseCompositeGlyph placed the first set of component glyphs on the + // component_point_count.gid_stack, which we start to process below. If a + // nested glyph is in turn a component glyph, additional glyphs are placed + // on the stack. + while (component_point_count.gid_stack.size()) { + GidAtLevel stack_top_gid = component_point_count.gid_stack.back(); + component_point_count.gid_stack.pop_back(); + + Buffer points_count_glyph(GetGlyphBufferSection( + data, + length, + offsets, + stack_top_gid.gid)); + + if (!points_count_glyph.buffer()) + return false; + + if (!points_count_glyph.length()) + continue; + + if (!TraverseComponentsCountingPoints(points_count_glyph, + i, + stack_top_gid.level, + &component_point_count)) { + return Error("Error validating component points and depth."); + } + + if (component_point_count.accumulated_component_points > + std::numeric_limits<uint16_t>::max()) { + return Error("Illegal composite points value " + "exceeding 0xFFFF for base glyph %d.", i); + } else if (this->maxp->version_1 && + component_point_count.accumulated_component_points > + this->maxp->max_c_points) { + Warning("Number of composite points in glyph %d exceeds " + "maxp maxCompositePoints: %d vs %d, adjusting limit.", + i, + component_point_count.accumulated_component_points, + this->maxp->max_c_points + ); + this->maxp->max_c_points = + component_point_count.accumulated_component_points; + } + } + } + + 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) { + this->iov.push_back(std::make_pair( + reinterpret_cast<const uint8_t*>("\x00\x00\x00\x00"), + static_cast<size_t>(padding))); + new_size += padding; + } + current_offset += new_size; + } + resulting_offsets[num_glyphs] = current_offset; + + const uint16_t max16 = std::numeric_limits<uint16_t>::max(); + if ((*std::max_element(resulting_offsets.begin(), + resulting_offsets.end()) >= (max16 * 2u)) && + (head->index_to_loc_format != 1)) { + head->index_to_loc_format = 1; + } + + loca->offsets = resulting_offsets; + + 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; +} + +bool OpenTypeGLYF::TraverseComponentsCountingPoints( + Buffer &glyph, + uint16_t base_glyph_id, + uint32_t level, + ComponentPointCount* component_point_count) { + + int16_t num_contours; + if (!glyph.ReadS16(&num_contours) || + !glyph.Skip(8)) { + return Error("Can't read glyph header."); + } + + if (num_contours <= -2) { + return Error("Bad number of contours %d in glyph.", num_contours); + } + + if (num_contours == 0) + return true; + + // FontTools counts a component level for each traversed recursion. We start + // counting at level 0. If we reach a level that's deeper than + // maxComponentDepth, we expand maxComponentDepth unless it's larger than + // the maximum possible depth. + if (level > std::numeric_limits<uint16_t>::max()) { + return Error("Illegal component depth exceeding 0xFFFF in base glyph id %d.", + base_glyph_id); + } else if (this->maxp->version_1 && + level > this->maxp->max_c_depth) { + this->maxp->max_c_depth = level; + Warning("Component depth exceeds maxp maxComponentDepth " + "in glyph %d, adjust limit to %d.", + base_glyph_id, level); + } + + if (num_contours > 0) { + uint16_t num_points = 0; + for (int i = 0; i < num_contours; ++i) { + // Simple glyph, add contour points. + uint16_t tmp_index = 0; + if (!glyph.ReadU16(&tmp_index)) { + return Error("Can't read contour index %d", i); + } + num_points = tmp_index + 1; + } + + component_point_count->accumulated_component_points += num_points; + return true; + } else { + assert(num_contours == -1); + + // Composite glyph, add gid's to stack. + 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"); + } + + size_t skip_bytes = 0; + skip_bytes += flags & ARG_1_AND_2_ARE_WORDS ? 4 : 2; + + if (flags & WE_HAVE_A_SCALE) { + skip_bytes += 2; + } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { + skip_bytes += 4; + } else if (flags & WE_HAVE_A_TWO_BY_TWO) { + skip_bytes += 8; + } + + if (!glyph.Skip(skip_bytes)) { + return Error("Failed to parse component glyph."); + } + + if (gid >= this->maxp->num_glyphs) { + return Error("Invalid glyph id used in composite glyph: %d", gid); + } + + component_point_count->gid_stack.push_back({gid, level + 1u}); + } while (flags & MORE_COMPONENTS); + return true; + } +} + +Buffer OpenTypeGLYF::GetGlyphBufferSection( + const uint8_t *data, + size_t length, + const std::vector<uint32_t>& loca_offsets, + unsigned glyph_id) { + + Buffer null_buffer(nullptr, 0); + + const unsigned gly_offset = loca_offsets[glyph_id]; + // The LOCA parser checks that these values are monotonic + const unsigned gly_length = loca_offsets[glyph_id + 1] - loca_offsets[glyph_id]; + if (!gly_length) { + // this glyph has no outline (e.g. the space character) + return Buffer(data + gly_offset, 0); + } + + if (gly_offset >= length) { + Error("Glyph %d offset %d too high %ld", glyph_id, gly_offset, length); + return null_buffer; + } + // Since these are unsigned types, the compiler is not allowed to assume + // that they never overflow. + if (gly_offset + gly_length < gly_offset) { + Error("Glyph %d length (%d < 0)!", glyph_id, gly_length); + return null_buffer; + } + if (gly_offset + gly_length > length) { + Error("Glyph %d length %d too high", glyph_id, gly_length); + return null_buffer; + } + + return Buffer(data + gly_offset, gly_length); +} + +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); + } + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/glyf.h b/gfx/ots/src/glyf.h new file mode 100644 index 0000000000..f85fdc4652 --- /dev/null +++ b/gfx/ots/src/glyf.h @@ -0,0 +1,82 @@ +// 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_GLYF_H_ +#define OTS_GLYF_H_ + +#include <new> +#include <utility> +#include <vector> + +#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) { } + + ~OpenTypeGLYF() { + for (auto* p : fixed_bboxes) { + delete[] p; + } + } + + bool Parse(const uint8_t *data, size_t length); + bool Serialize(OTSStream *out); + + private: + struct GidAtLevel { + uint16_t gid; + uint32_t level; + }; + + struct ComponentPointCount { + ComponentPointCount() : accumulated_component_points(0) {}; + uint32_t accumulated_component_points; + std::vector<GidAtLevel> gid_stack; + }; + + bool ParseFlagsForSimpleGlyph(Buffer &glyph, + uint32_t num_flags, + std::vector<uint8_t>& flags, + uint32_t *flag_index, + uint32_t *coordinates_length); + bool ParseSimpleGlyph(Buffer &glyph, + unsigned gid, + int16_t num_contours, + int16_t& xmin, + int16_t& ymin, + int16_t& xmax, + int16_t& ymax, + bool is_tricky_font); + bool ParseCompositeGlyph( + Buffer &glyph, + ComponentPointCount* component_point_count); + + + bool TraverseComponentsCountingPoints( + Buffer& glyph, + uint16_t base_glyph_id, + uint32_t level, + ComponentPointCount* component_point_count); + + Buffer GetGlyphBufferSection( + const uint8_t *data, + size_t length, + const std::vector<uint32_t>& loca_offsets, + unsigned glyph_id); + + OpenTypeMAXP* maxp; + + std::vector<std::pair<const uint8_t*, size_t> > iov; + + std::vector<uint8_t*> fixed_bboxes; +}; + +} // namespace ots + +#endif // OTS_GLYF_H_ diff --git a/gfx/ots/src/gpos.cc b/gfx/ots/src/gpos.cc new file mode 100644 index 0000000000..fefab154e3 --- /dev/null +++ b/gfx/ots/src/gpos.cc @@ -0,0 +1,711 @@ +// 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 "gpos.h" + +#include <limits> +#include <vector> + +#include "layout.h" +#include "maxp.h" + +// GPOS - The Glyph Positioning Table +// http://www.microsoft.com/typography/otspec/gpos.htm + +#define TABLE_NAME "GPOS" + +namespace { + +enum GPOS_TYPE { + GPOS_TYPE_SINGLE_ADJUSTMENT = 1, + GPOS_TYPE_PAIR_ADJUSTMENT = 2, + GPOS_TYPE_CURSIVE_ATTACHMENT = 3, + GPOS_TYPE_MARK_TO_BASE_ATTACHMENT = 4, + GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT = 5, + GPOS_TYPE_MARK_TO_MARK_ATTACHMENT = 6, + GPOS_TYPE_CONTEXT_POSITIONING = 7, + GPOS_TYPE_CHAINED_CONTEXT_POSITIONING = 8, + GPOS_TYPE_EXTENSION_POSITIONING = 9, + GPOS_TYPE_RESERVED = 10 +}; + +// The maximum format number for anchor tables. +const uint16_t kMaxAnchorFormat = 3; + +// 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 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) { + // Just read the field since these fileds could take an arbitrary values. + if (!subtable->Skip(2)) { + return OTS_FAILURE_MSG("Failed to read value reacord component"); + } + } + } + + // Check existence of offsets to device table. + for (unsigned i = 0; i < 4; ++i) { + if ((value_format >> (i + 4)) & 0x1) { + uint16_t offset = 0; + if (!subtable->ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read value record offset"); + } + if (offset) { + // TODO(bashi): Is it possible that device tables locate before + // this record? No fonts contain such offset AKAIF. + if (offset >= length) { + return OTS_FAILURE_MSG("Value record offset too high %d >= %ld", offset, length); + } + if (!ots::ParseDeviceTable(font, data + offset, length - offset)) { + return OTS_FAILURE_MSG("Failed to parse device table in value record"); + } + } + } + } + return true; +} + +bool ParseAnchorTable(const ots::Font *font, + const uint8_t *data, const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + // Read format and skip 2 2-byte fields that could be arbitrary values. + if (!subtable.ReadU16(&format) || + !subtable.Skip(4)) { + return OTS_FAILURE_MSG("Faled to read anchor table"); + } + + if (format == 0 || format > kMaxAnchorFormat) { + return OTS_FAILURE_MSG("Bad Anchor table format %d", format); + } + + // Format 2 and 3 has additional fields. + if (format == 2) { + // Format 2 provides an index to a glyph contour point, which will take + // arbitrary value. + uint16_t anchor_point = 0; + if (!subtable.ReadU16(&anchor_point)) { + return OTS_FAILURE_MSG("Failed to read anchor point in format 2 Anchor Table"); + } + } else if (format == 3) { + uint16_t offset_x_device = 0; + uint16_t offset_y_device = 0; + if (!subtable.ReadU16(&offset_x_device) || + !subtable.ReadU16(&offset_y_device)) { + return OTS_FAILURE_MSG("Failed to read device table offsets in format 3 anchor table"); + } + const unsigned format_end = static_cast<unsigned>(10); + if (offset_x_device) { + if (offset_x_device < format_end || offset_x_device >= length) { + return OTS_FAILURE_MSG("Bad x device table offset %d", offset_x_device); + } + if (!ots::ParseDeviceTable(font, data + offset_x_device, + length - offset_x_device)) { + return OTS_FAILURE_MSG("Failed to parse device table in anchor table"); + } + } + if (offset_y_device) { + if (offset_y_device < format_end || offset_y_device >= length) { + return OTS_FAILURE_MSG("Bad y device table offset %d", offset_y_device); + } + if (!ots::ParseDeviceTable(font, data + offset_y_device, + length - offset_y_device)) { + return OTS_FAILURE_MSG("Failed to parse device table in anchor table"); + } + } + } + return true; +} + +bool ParseMarkArrayTable(const ots::Font *font, + const uint8_t *data, const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t mark_count = 0; + if (!subtable.ReadU16(&mark_count)) { + return OTS_FAILURE_MSG("Can't read mark table length"); + } + + // MarkRecord consists of 4-bytes. + const unsigned mark_records_end = 4 * static_cast<unsigned>(mark_count) + 2; + if (mark_records_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad mark table length"); + } + for (unsigned i = 0; i < mark_count; ++i) { + uint16_t class_value = 0; + uint16_t offset_mark_anchor = 0; + if (!subtable.ReadU16(&class_value) || + !subtable.ReadU16(&offset_mark_anchor)) { + return OTS_FAILURE_MSG("Can't read mark table %d", i); + } + // |class_value| may take arbitrary values including 0 here so we don't + // check the value. + if (offset_mark_anchor < mark_records_end || + offset_mark_anchor >= length) { + return OTS_FAILURE_MSG("Bad mark anchor offset %d for mark table %d", offset_mark_anchor, i); + } + if (!ParseAnchorTable(font, data + offset_mark_anchor, + length - offset_mark_anchor)) { + return OTS_FAILURE_MSG("Faled to parse anchor table for mark table %d", i); + } + } + + return true; +} + +bool ParsePairSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t value_format1, + const uint16_t value_format2, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t value_count = 0; + if (!subtable.ReadU16(&value_count)) { + return OTS_FAILURE_MSG("Failed to read pair set table structure"); + } + for (unsigned i = 0; i < value_count; ++i) { + // Check pair value record. + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read glyph in pair value record %d", i); + } + if (glyph_id >= num_glyphs) { + return OTS_FAILURE_MSG("glyph id %d too high >= %d", glyph_id, num_glyphs); + } + if (!ParseValueRecord(font, &subtable, value_format1)) { + return OTS_FAILURE_MSG("Failed to parse value record in format 1 pair set table"); + } + if (!ParseValueRecord(font, &subtable, value_format2)) { + return OTS_FAILURE_MSG("Failed to parse value record in format 2 pair set table"); + } + } + return true; +} + +bool ParsePairPosFormat1(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t value_format1, + const uint16_t value_format2, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Skip 8 bytes that are already read before. + if (!subtable.Skip(8)) { + return OTS_FAILURE_MSG("Failed to read pair pos table structure"); + } + + uint16_t pair_set_count = 0; + if (!subtable.ReadU16(&pair_set_count)) { + return OTS_FAILURE_MSG("Failed to read pair pos set count"); + } + + const unsigned pair_pos_end = 2 * static_cast<unsigned>(pair_set_count) + 10; + if (pair_pos_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad pair set length %d", pair_pos_end); + } + for (unsigned i = 0; i < pair_set_count; ++i) { + uint16_t pair_set_offset = 0; + if (!subtable.ReadU16(&pair_set_offset)) { + return OTS_FAILURE_MSG("Failed to read pair set offset for pair set %d", i); + } + if (pair_set_offset < pair_pos_end || pair_set_offset >= length) { + return OTS_FAILURE_MSG("Bad pair set offset %d for pair set %d", pair_set_offset, i); + } + // Check pair set tables + if (!ParsePairSetTable(font, data + pair_set_offset, length - pair_set_offset, + value_format1, value_format2, + num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse pair set table %d", i); + } + } + + return true; +} + +bool ParsePairPosFormat2(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t value_format1, + const uint16_t value_format2, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Skip 8 bytes that are already read before. + if (!subtable.Skip(8)) { + return OTS_FAILURE_MSG("Failed to read pair pos format 2 structure"); + } + + uint16_t offset_class_def1 = 0; + uint16_t offset_class_def2 = 0; + uint16_t class1_count = 0; + uint16_t class2_count = 0; + if (!subtable.ReadU16(&offset_class_def1) || + !subtable.ReadU16(&offset_class_def2) || + !subtable.ReadU16(&class1_count) || + !subtable.ReadU16(&class2_count)) { + return OTS_FAILURE_MSG("Failed to read pair pos format 2 data"); + } + + 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. + 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 (!ots::ParseClassDefTable(font, data + offset_class_def1, + length - offset_class_def1, + 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, ots::kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse class definition table 2"); + } + + return true; +} + +bool ParseAnchorArrayTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t class_count) { + ots::Buffer subtable(data, length); + + uint16_t record_count = 0; + if (!subtable.ReadU16(&record_count)) { + return OTS_FAILURE_MSG("Can't read anchor array length"); + } + + const unsigned anchor_array_end = 2 * static_cast<unsigned>(record_count) * + static_cast<unsigned>(class_count) + 2; + if (anchor_array_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of anchor array %d", anchor_array_end); + } + for (unsigned i = 0; i < record_count; ++i) { + for (unsigned j = 0; j < class_count; ++j) { + uint16_t offset_record = 0; + if (!subtable.ReadU16(&offset_record)) { + return OTS_FAILURE_MSG("Can't read anchor array record offset for class %d and record %d", j, i); + } + // |offset_record| could be NULL. + if (offset_record) { + if (offset_record < anchor_array_end || offset_record >= length) { + return OTS_FAILURE_MSG("Bad record offset %d in class %d, record %d", offset_record, j, i); + } + if (!ParseAnchorTable(font, data + offset_record, + length - offset_record)) { + return OTS_FAILURE_MSG("Failed to parse anchor table for class %d, record %d", j, i); + } + } + } + } + return true; +} + +bool ParseLigatureArrayTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t class_count) { + ots::Buffer subtable(data, length); + + uint16_t ligature_count = 0; + if (!subtable.ReadU16(&ligature_count)) { + return OTS_FAILURE_MSG("Failed to read ligature count"); + } + for (unsigned i = 0; i < ligature_count; ++i) { + uint16_t offset_ligature_attach = 0; + if (!subtable.ReadU16(&offset_ligature_attach)) { + return OTS_FAILURE_MSG("Can't read ligature offset %d", i); + } + if (offset_ligature_attach < 2 || offset_ligature_attach >= length) { + return OTS_FAILURE_MSG("Bad ligature attachment offset %d in ligature %d", offset_ligature_attach, i); + } + if (!ParseAnchorArrayTable(font, data + offset_ligature_attach, + length - offset_ligature_attach, class_count)) { + return OTS_FAILURE_MSG("Failed to parse anchor table for ligature %d", i); + } + } + return true; +} + +// Common parser for Lookup Type 4, 5 and 6. +bool ParseMarkToAttachmentSubtables(const ots::Font *font, + const uint8_t *data, const size_t length, + const GPOS_TYPE type) { + ots::Buffer subtable(data, length); + + ots::OpenTypeMAXP *maxp = static_cast<ots::OpenTypeMAXP*>( + 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; + uint16_t class_count = 0; + uint16_t offset_mark_array = 0; + uint16_t offset_type_specific_array = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage1) || + !subtable.ReadU16(&offset_coverage2) || + !subtable.ReadU16(&class_count) || + !subtable.ReadU16(&offset_mark_array) || + !subtable.ReadU16(&offset_type_specific_array)) { + return OTS_FAILURE_MSG("Failed to read mark attachment subtable header"); + } + + if (format != 1) { + return OTS_FAILURE_MSG("bad mark attachment subtable format %d", format); + } + + const unsigned header_end = static_cast<unsigned>(subtable.offset()); + if (header_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad mark attachment subtable size ending at %d", header_end); + } + if (offset_coverage1 < header_end || offset_coverage1 >= length) { + return OTS_FAILURE_MSG("Bad coverage 1 offset %d", offset_coverage1); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage1, + length - offset_coverage1, + maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse converge 1 table"); + } + if (offset_coverage2 < header_end || offset_coverage2 >= length) { + return OTS_FAILURE_MSG("Bad coverage 2 offset %d", offset_coverage2); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage2, + length - offset_coverage2, + maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table 2"); + } + + if (offset_mark_array < header_end || offset_mark_array >= length) { + return OTS_FAILURE_MSG("Bad mark array offset %d", offset_mark_array); + } + if (!ParseMarkArrayTable(font, data + offset_mark_array, + length - offset_mark_array)) { + return OTS_FAILURE_MSG("Failed to parse mark array"); + } + + if (offset_type_specific_array < header_end || + offset_type_specific_array >= length) { + return OTS_FAILURE_MSG("Bad type specific array offset %d", offset_type_specific_array); + } + if (type == GPOS_TYPE_MARK_TO_BASE_ATTACHMENT || + type == GPOS_TYPE_MARK_TO_MARK_ATTACHMENT) { + if (!ParseAnchorArrayTable(font, data + offset_type_specific_array, + length - offset_type_specific_array, + class_count)) { + return OTS_FAILURE_MSG("Failed to parse anchor array"); + } + } else if (type == GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT) { + if (!ParseLigatureArrayTable(font, data + offset_type_specific_array, + length - offset_type_specific_array, + class_count)) { + return OTS_FAILURE_MSG("Failed to parse ligature array"); + } + } else { + return OTS_FAILURE_MSG("Bad attachment type %d", type); + } + + return true; +} + +} // namespace + +namespace ots { + +// Lookup Type 1: +// Single Adjustment Positioning Subtable +bool OpenTypeGPOS::ParseSingleAdjustment(const uint8_t *data, + const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t value_format = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&value_format)) { + return Error("Can't read single adjustment information"); + } + + if (format == 1) { + // Format 1 exactly one value record. + if (!ParseValueRecord(font, &subtable, value_format)) { + return Error("Failed to parse format 1 single adjustment table"); + } + } else if (format == 2) { + uint16_t value_count = 0; + if (!subtable.ReadU16(&value_count)) { + return Error("Failed to parse format 2 single adjustment table"); + } + for (unsigned i = 0; i < value_count; ++i) { + if (!ParseValueRecord(font, &subtable, value_format)) { + return Error("Failed to parse value record %d in format 2 single adjustment table", i); + } + } + } else { + return Error("Bad format %d in single adjustment table", format); + } + + if (offset_coverage < subtable.offset() || offset_coverage >= length) { + return Error("Bad coverage offset %d in single adjustment table", offset_coverage); + } + + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, + maxp->num_glyphs)) { + return Error("Failed to parse coverage table in single adjustment table"); + } + + return true; +} + +// Lookup Type 2: +// Pair Adjustment Positioning Subtable +bool OpenTypeGPOS::ParsePairAdjustment(const uint8_t *data, + const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t value_format1 = 0; + uint16_t value_format2 = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&value_format1) || + !subtable.ReadU16(&value_format2)) { + return Error("Failed to read pair adjustment structure"); + } + + if (format == 1) { + if (!ParsePairPosFormat1(font, data, length, value_format1, value_format2, + maxp->num_glyphs)) { + return Error("Failed to parse pair pos format 1"); + } + } else if (format == 2) { + if (!ParsePairPosFormat2(font, data, length, value_format1, value_format2, + maxp->num_glyphs)) { + return Error("Failed to parse pair format 2"); + } + } else { + return Error("Bad pos pair format %d", format); + } + + if (offset_coverage < subtable.offset() || offset_coverage >= length) { + return Error("Bad pair pos offset coverage %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, + maxp->num_glyphs)) { + return Error("Failed to parse coverage table"); + } + + return true; +} + +// Lookup Type 3 +// Cursive Attachment Positioning Subtable +bool OpenTypeGPOS::ParseCursiveAttachment(const uint8_t *data, + const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t entry_exit_count = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&entry_exit_count)) { + return Error("Failed to read cursive attachment structure"); + } + + if (format != 1) { + return Error("Bad cursive attachment format %d", format); + } + + // Check entry exit records. + const unsigned entry_exit_records_end = + 2 * static_cast<unsigned>(entry_exit_count) + 6; + if (entry_exit_records_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad entry exit record end %d", entry_exit_records_end); + } + for (unsigned i = 0; i < entry_exit_count; ++i) { + uint16_t offset_entry_anchor = 0; + uint16_t offset_exit_anchor = 0; + if (!subtable.ReadU16(&offset_entry_anchor) || + !subtable.ReadU16(&offset_exit_anchor)) { + return Error("Can't read entry exit record %d", i); + } + // These offsets could be NULL. + if (offset_entry_anchor) { + if (offset_entry_anchor < entry_exit_records_end || + offset_entry_anchor >= length) { + return Error("Bad entry anchor offset %d in entry exit record %d", offset_entry_anchor, i); + } + if (!ParseAnchorTable(font, data + offset_entry_anchor, + length - offset_entry_anchor)) { + return Error("Failed to parse entry anchor table in entry exit record %d", i); + } + } + if (offset_exit_anchor) { + if (offset_exit_anchor < entry_exit_records_end || + offset_exit_anchor >= length) { + return Error("Bad exit anchor offset %d in entry exit record %d", offset_exit_anchor, i); + } + if (!ParseAnchorTable(font, data + offset_exit_anchor, + length - offset_exit_anchor)) { + return Error("Failed to parse exit anchor table in entry exit record %d", i); + } + } + } + + if (offset_coverage < subtable.offset() || offset_coverage >= length) { + return Error("Bad coverage offset in cursive attachment %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, + maxp->num_glyphs)) { + return Error("Failed to parse coverage table in cursive attachment"); + } + + return true; +} + +// Lookup Type 4: +// MarkToBase Attachment Positioning Subtable +bool OpenTypeGPOS::ParseMarkToBaseAttachment(const uint8_t *data, + const size_t length) { + return ParseMarkToAttachmentSubtables(GetFont(), data, length, + GPOS_TYPE_MARK_TO_BASE_ATTACHMENT); +} + +// Lookup Type 5: +// MarkToLigature Attachment Positioning Subtable +bool OpenTypeGPOS::ParseMarkToLigatureAttachment(const uint8_t *data, + const size_t length) { + return ParseMarkToAttachmentSubtables(GetFont(), data, length, + GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT); +} + +// Lookup Type 6: +// MarkToMark Attachment Positioning Subtable +bool OpenTypeGPOS::ParseMarkToMarkAttachment(const uint8_t *data, + const size_t length) { + return ParseMarkToAttachmentSubtables(GetFont(), data, length, + GPOS_TYPE_MARK_TO_MARK_ATTACHMENT); +} + +// Lookup Type 7: +// Contextual Positioning Subtables +// OpenTypeLayoutTable::ParseContextSubtable() + +// Lookup Type 8: +// Chaining Contexual Positioning Subtable +// OpenTypeLayoutTable::ParseChainingContextSubtable() + +// Lookup Type 9: +// Extension Positioning +// OpenTypeLayoutTable::ParseExtensionSubtable + + +bool OpenTypeGPOS::ValidLookupSubtableType(const uint16_t lookup_type, + bool extension) const { + if (extension && lookup_type == GPOS_TYPE_EXTENSION_POSITIONING) + return false; + return lookup_type >= GPOS_TYPE_SINGLE_ADJUSTMENT && lookup_type < GPOS_TYPE_RESERVED; +} + +bool OpenTypeGPOS::ParseLookupSubtable(const uint8_t *data, const size_t length, + const uint16_t lookup_type) { + switch (lookup_type) { + case GPOS_TYPE_SINGLE_ADJUSTMENT: + return ParseSingleAdjustment(data, length); + case GPOS_TYPE_PAIR_ADJUSTMENT: + return ParsePairAdjustment(data, length); + case GPOS_TYPE_CURSIVE_ATTACHMENT: + return ParseCursiveAttachment(data, length); + case GPOS_TYPE_MARK_TO_BASE_ATTACHMENT: + return ParseMarkToBaseAttachment(data, length); + case GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT: + return ParseMarkToLigatureAttachment(data, length); + case GPOS_TYPE_MARK_TO_MARK_ATTACHMENT: + return ParseMarkToMarkAttachment(data, length); + case GPOS_TYPE_CONTEXT_POSITIONING: + return ParseContextSubtable(data, length); + case GPOS_TYPE_CHAINED_CONTEXT_POSITIONING: + return ParseChainingContextSubtable(data, length); + case GPOS_TYPE_EXTENSION_POSITIONING: + return ParseExtensionSubtable(data, length); + } + return false; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/gpos.h b/gfx/ots/src/gpos.h new file mode 100644 index 0000000000..179bee828b --- /dev/null +++ b/gfx/ots/src/gpos.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef OTS_GPOS_H_ +#define OTS_GPOS_H_ + +#include "ots.h" +#include "layout.h" + +namespace ots { + +class OpenTypeGPOS : public OpenTypeLayoutTable { + public: + explicit OpenTypeGPOS(Font *font, uint32_t tag) + : OpenTypeLayoutTable(font, tag, tag) { } + + private: + bool ValidLookupSubtableType(const uint16_t lookup_type, + bool extension = false) const; + bool ParseLookupSubtable(const uint8_t *data, const size_t length, + const uint16_t lookup_type); + + bool ParseSingleAdjustment(const uint8_t *data, const size_t length); + bool ParsePairAdjustment(const uint8_t *data, const size_t length); + bool ParseCursiveAttachment(const uint8_t *data, const size_t length); + bool ParseMarkToBaseAttachment(const uint8_t *data, const size_t length); + bool ParseMarkToLigatureAttachment(const uint8_t *data, const size_t length); + bool ParseMarkToMarkAttachment(const uint8_t *data, const size_t length); +}; + +} // namespace ots + +#endif + diff --git a/gfx/ots/src/graphite.h b/gfx/ots/src/graphite.h new file mode 100644 index 0000000000..452cb26a87 --- /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 <vector> +#include <type_traits> + +namespace ots { + +template<typename ParentType> +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<typename T> +bool SerializeParts(const std::vector<T>& vec, OTSStream* out) { + for (const T& part : vec) { + if (!part.SerializePart(out)) { + return false; + } + } + return true; +} + +template<typename T> +bool SerializeParts(const std::vector<std::vector<T>>& vec, OTSStream* out) { + for (const std::vector<T>& part : vec) { + if (!SerializeParts(part, out)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector<uint8_t>& vec, OTSStream* out) { + for (uint8_t part : vec) { + if (!out->WriteU8(part)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector<uint16_t>& vec, OTSStream* out) { + for (uint16_t part : vec) { + if (!out->WriteU16(part)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector<int16_t>& vec, OTSStream* out) { + for (int16_t part : vec) { + if (!out->WriteS16(part)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector<uint32_t>& vec, OTSStream* out) { + for (uint32_t part : vec) { + if (!out->WriteU32(part)) { + return false; + } + } + return true; +} + +inline bool SerializeParts(const std::vector<int32_t>& vec, OTSStream* out) { + for (int32_t part : vec) { + if (!out->WriteS32(part)) { + return false; + } + } + return true; +} + +template<typename T> +size_t datasize(std::vector<T> 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 new file mode 100644 index 0000000000..ea6e305427 --- /dev/null +++ b/gfx/ots/src/gsub.cc @@ -0,0 +1,533 @@ +// 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 "gsub.h" + +#include <limits> +#include <vector> + +#include "layout.h" +#include "maxp.h" + +// GSUB - The Glyph Substitution Table +// http://www.microsoft.com/typography/otspec/gsub.htm + +#define TABLE_NAME "GSUB" + +namespace { + +enum GSUB_TYPE { + GSUB_TYPE_SINGLE = 1, + GSUB_TYPE_MULTIPLE = 2, + GSUB_TYPE_ALTERNATE = 3, + GSUB_TYPE_LIGATURE = 4, + GSUB_TYPE_CONTEXT = 5, + GSUB_TYPE_CHANGING_CONTEXT = 6, + GSUB_TYPE_EXTENSION_SUBSTITUTION = 7, + GSUB_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE = 8, + GSUB_TYPE_RESERVED = 9 +}; + +bool ParseSequenceTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read glyph count in sequence table"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("bad glyph count %d > %d", glyph_count, num_glyphs); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t substitute = 0; + if (!subtable.ReadU16(&substitute)) { + return OTS_FAILURE_MSG("Failed to read substitution %d in sequence table", i); + } + if (substitute >= num_glyphs) { + return OTS_FAILURE_MSG("Bad substitution (%d) %d > %d", i, substitute, num_glyphs); + } + } + + return true; +} + +bool ParseAlternateSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read alternate set header"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count %d > %d in alternate set table", glyph_count, num_glyphs); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t alternate = 0; + if (!subtable.ReadU16(&alternate)) { + return OTS_FAILURE_MSG("Can't read alternate %d", i); + } + if (alternate >= num_glyphs) { + return OTS_FAILURE_MSG("Too large alternate: %u", alternate); + } + } + return true; +} + +bool ParseLigatureTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t lig_glyph = 0; + uint16_t comp_count = 0; + + if (!subtable.ReadU16(&lig_glyph) || + !subtable.ReadU16(&comp_count)) { + return OTS_FAILURE_MSG("Failed to read ligature table header"); + } + + if (lig_glyph >= num_glyphs) { + return OTS_FAILURE_MSG("too large lig_glyph: %u", lig_glyph); + } + if (comp_count == 0) { + return OTS_FAILURE_MSG("Component count cannot be 0"); + } + for (unsigned i = 0; i < comp_count - static_cast<unsigned>(1); ++i) { + uint16_t component = 0; + if (!subtable.ReadU16(&component)) { + return OTS_FAILURE_MSG("Can't read ligature component %d", i); + } + if (component >= num_glyphs) { + return OTS_FAILURE_MSG("Bad ligature component %d of %d", i, component); + } + } + + return true; +} + +bool ParseLigatureSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t ligature_count = 0; + + if (!subtable.ReadU16(&ligature_count)) { + return OTS_FAILURE_MSG("Can't read ligature count in ligature set"); + } + + const unsigned ligature_end = static_cast<unsigned>(2) + ligature_count * 2; + if (ligature_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of ligature %d in ligature set", ligature_end); + } + for (unsigned i = 0; i < ligature_count; ++i) { + uint16_t offset_ligature = 0; + if (!subtable.ReadU16(&offset_ligature)) { + return OTS_FAILURE_MSG("Failed to read ligature offset %d", i); + } + if (offset_ligature < ligature_end || offset_ligature >= length) { + return OTS_FAILURE_MSG("Bad ligature offset %d for ligature %d", offset_ligature, i); + } + if (!ParseLigatureTable(font, data + offset_ligature, length - offset_ligature, + num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse ligature %d", i); + } + } + + return true; +} + +} // namespace + +namespace ots { + +// Lookup Type 1: +// Single Substitution Subtable +bool OpenTypeGSUB::ParseSingleSubstitution(const uint8_t *data, + const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage)) { + return Error("Failed to read single subst table header"); + } + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; + if (format == 1) { + // Parse SingleSubstFormat1 + int16_t delta_glyph_id = 0; + if (!subtable.ReadS16(&delta_glyph_id)) { + return Error("Failed to read glyph shift from format 1 single subst table"); + } + if (std::abs(delta_glyph_id) >= num_glyphs) { + return Error("bad glyph shift of %d in format 1 single subst table", delta_glyph_id); + } + } else if (format == 2) { + // Parse SingleSubstFormat2 + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return Error("Failed to read glyph cound in format 2 single subst table"); + } + if (glyph_count > num_glyphs) { + return Error("Bad glyph count %d > %d in format 2 single subst table", glyph_count, num_glyphs); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t substitute = 0; + if (!subtable.ReadU16(&substitute)) { + return Error("Failed to read substitution %d in format 2 single subst table", i); + } + if (substitute >= num_glyphs) { + return Error("too large substitute: %u", substitute); + } + } + } else { + return Error("Bad single subst table format %d", format); + } + + if (offset_coverage < subtable.offset() || offset_coverage >= length) { + return Error("Bad coverage offset %x", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return Error("Failed to parse coverage table"); + } + + return true; +} + +// Lookup Type 2: +// Multiple Substitution Subtable +bool OpenTypeGSUB::ParseMutipleSubstitution(const uint8_t *data, + const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t sequence_count = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&sequence_count)) { + return Error("Can't read header of multiple subst table"); + } + + if (format != 1) { + return Error("Bad multiple subst table format %d", format); + } + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; + const unsigned sequence_end = static_cast<unsigned>(6) + + sequence_count * 2; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad sequence end %d, in multiple subst", sequence_end); + } + for (unsigned i = 0; i < sequence_count; ++i) { + uint16_t offset_sequence = 0; + if (!subtable.ReadU16(&offset_sequence)) { + return Error("Failed to read sequence offset for sequence %d", i); + } + if (offset_sequence < sequence_end || offset_sequence >= length) { + return Error("Bad sequence offset %d for sequence %d", offset_sequence, i); + } + if (!ParseSequenceTable(font, data + offset_sequence, length - offset_sequence, + num_glyphs)) { + return Error("Failed to parse sequence table %d", i); + } + } + + if (offset_coverage < sequence_end || offset_coverage >= length) { + return Error("Bad coverage offset %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return Error("Failed to parse coverage table"); + } + + return true; +} + +// Lookup Type 3: +// Alternate Substitution Subtable +bool OpenTypeGSUB::ParseAlternateSubstitution(const uint8_t *data, + const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t alternate_set_count = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&alternate_set_count)) { + return Error("Can't read alternate subst header"); + } + + if (format != 1) { + return Error("Bad alternate subst table format %d", format); + } + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; + const unsigned alternate_set_end = static_cast<unsigned>(6) + + alternate_set_count * 2; + if (alternate_set_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad end of alternate set %d", alternate_set_end); + } + for (unsigned i = 0; i < alternate_set_count; ++i) { + uint16_t offset_alternate_set = 0; + if (!subtable.ReadU16(&offset_alternate_set)) { + return Error("Can't read alternate set offset for set %d", i); + } + if (offset_alternate_set < alternate_set_end || + offset_alternate_set >= length) { + return Error("Bad alternate set offset %d for set %d", offset_alternate_set, i); + } + if (!ParseAlternateSetTable(font, data + offset_alternate_set, + length - offset_alternate_set, + num_glyphs)) { + return Error("Failed to parse alternate set"); + } + } + + if (offset_coverage < alternate_set_end || offset_coverage >= length) { + return Error("Bad coverage offset %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return Error("Failed to parse coverage table"); + } + + return true; +} + +// Lookup Type 4: +// Ligature Substitution Subtable +bool OpenTypeGSUB::ParseLigatureSubstitution(const uint8_t *data, + const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t lig_set_count = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&lig_set_count)) { + return Error("Failed to read ligature substitution header"); + } + + if (format != 1) { + return Error("Bad ligature substitution table format %d", format); + } + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; + const unsigned ligature_set_end = static_cast<unsigned>(6) + + lig_set_count * 2; + if (ligature_set_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad end of ligature set %d in ligature substitution table", ligature_set_end); + } + for (unsigned i = 0; i < lig_set_count; ++i) { + uint16_t offset_ligature_set = 0; + if (!subtable.ReadU16(&offset_ligature_set)) { + return Error("Can't read ligature set offset %d", i); + } + if (offset_ligature_set < ligature_set_end || + offset_ligature_set >= length) { + return Error("Bad ligature set offset %d for set %d", offset_ligature_set, i); + } + if (!ParseLigatureSetTable(font, data + offset_ligature_set, + length - offset_ligature_set, num_glyphs)) { + return Error("Failed to parse ligature set %d", i); + } + } + + if (offset_coverage < ligature_set_end || offset_coverage >= length) { + return Error("Bad coverage offset %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return Error("Failed to parse coverage table"); + } + + return true; +} + +// Lookup Type 5: +// Contextual Substitution Subtable +// OpenTypeLayoutTable::ParseContextSubtable() + +// Lookup Type 6: +// Chaining Contextual Substitution Subtable +// OpenTypeLayoutTable::ParseChainingContextSubtable + +// Lookup Type 7: +// Extension Substition +// OpenTypeLayoutTable::ParseExtensionSubtable + +// Lookup Type 8: +// Reverse Chaining Contexual Single Substitution Subtable +bool OpenTypeGSUB::ParseReverseChainingContextSingleSubstitution(const uint8_t *data, + const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage)) { + return Error("Failed to read reverse chaining header"); + } + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; + + uint16_t backtrack_glyph_count = 0; + if (!subtable.ReadU16(&backtrack_glyph_count)) { + return Error("Failed to read backtrack glyph count in reverse chaining table"); + } + std::vector<uint16_t> offsets_backtrack; + offsets_backtrack.reserve(backtrack_glyph_count); + for (unsigned i = 0; i < backtrack_glyph_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return Error("Failed to read backtrack offset %d", i); + } + offsets_backtrack.push_back(offset); + } + + uint16_t lookahead_glyph_count = 0; + if (!subtable.ReadU16(&lookahead_glyph_count)) { + return Error("Failed to read look ahead glyph count"); + } + std::vector<uint16_t> offsets_lookahead; + offsets_lookahead.reserve(lookahead_glyph_count); + for (unsigned i = 0; i < lookahead_glyph_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return Error("Can't read look ahead offset %d", i); + } + offsets_lookahead.push_back(offset); + } + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return Error("Can't read glyph count in reverse chaining table"); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t substitute = 0; + if (!subtable.ReadU16(&substitute)) { + return Error("Failed to read substitution %d reverse chaining table", i); + } + if (substitute >= num_glyphs) { + return Error("Bad substitute glyph %d in reverse chaining table substitution %d", substitute, i); + } + } + + const unsigned substitute_end = static_cast<unsigned>(10) + + (backtrack_glyph_count + lookahead_glyph_count + glyph_count) * 2; + if (substitute_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad substitute end offset in reverse chaining table"); + } + + if (offset_coverage < substitute_end || offset_coverage >= length) { + return Error("Bad coverage offset %d in reverse chaining table", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return Error("Failed to parse coverage table in reverse chaining table"); + } + + for (unsigned i = 0; i < backtrack_glyph_count; ++i) { + if (offsets_backtrack[i] < substitute_end || + offsets_backtrack[i] >= length) { + return Error("Bad backtrack offset %d for backtrack %d in reverse chaining table", offsets_backtrack[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_backtrack[i], + length - offsets_backtrack[i], num_glyphs)) { + return Error("Failed to parse coverage table for backtrack %d in reverse chaining table", i); + } + } + + for (unsigned i = 0; i < lookahead_glyph_count; ++i) { + if (offsets_lookahead[i] < substitute_end || + offsets_lookahead[i] >= length) { + return Error("Bad lookahead offset %d for lookahead %d in reverse chaining table", offsets_lookahead[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_lookahead[i], + length - offsets_lookahead[i], num_glyphs)) { + return Error("Failed to parse lookahead coverage table %d in reverse chaining table", i); + } + } + + return true; +} + +bool OpenTypeGSUB::ValidLookupSubtableType(const uint16_t lookup_type, + bool extension) const { + if (extension && lookup_type == GSUB_TYPE_EXTENSION_SUBSTITUTION) + return false; + return lookup_type >= GSUB_TYPE_SINGLE && lookup_type < GSUB_TYPE_RESERVED; +} + +bool OpenTypeGSUB::ParseLookupSubtable(const uint8_t *data, const size_t length, + const uint16_t lookup_type) { + switch (lookup_type) { + case GSUB_TYPE_SINGLE: + return ParseSingleSubstitution(data, length); + case GSUB_TYPE_MULTIPLE: + return ParseMutipleSubstitution(data, length); + case GSUB_TYPE_ALTERNATE: + return ParseAlternateSubstitution(data, length); + case GSUB_TYPE_LIGATURE: + return ParseLigatureSubstitution(data, length); + case GSUB_TYPE_CONTEXT: + return ParseContextSubtable(data, length); + case GSUB_TYPE_CHANGING_CONTEXT: + return ParseChainingContextSubtable(data, length); + case GSUB_TYPE_EXTENSION_SUBSTITUTION: + return ParseExtensionSubtable(data, length); + case GSUB_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE: + return ParseReverseChainingContextSingleSubstitution(data, length); + } + return false; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/gsub.h b/gfx/ots/src/gsub.h new file mode 100644 index 0000000000..9ceb5c7170 --- /dev/null +++ b/gfx/ots/src/gsub.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef OTS_GSUB_H_ +#define OTS_GSUB_H_ + +#include "ots.h" +#include "layout.h" + +namespace ots { + +class OpenTypeGSUB : public OpenTypeLayoutTable { + public: + explicit OpenTypeGSUB(Font *font, uint32_t tag) + : OpenTypeLayoutTable(font, tag, tag) { } + + private: + bool ValidLookupSubtableType(uint16_t lookup_type, + bool extension = false) const; + bool ParseLookupSubtable(const uint8_t *data, const size_t length, + const uint16_t lookup_type); + + bool ParseSingleSubstitution(const uint8_t *data, const size_t length); + bool ParseMutipleSubstitution(const uint8_t *data, const size_t length); + bool ParseAlternateSubstitution(const uint8_t *data, const size_t length); + bool ParseLigatureSubstitution(const uint8_t *data, const size_t length); + bool ParseReverseChainingContextSingleSubstitution(const uint8_t *data, const size_t length); +}; + +} // namespace ots + +#endif // OTS_GSUB_H_ + diff --git a/gfx/ots/src/gvar.cc b/gfx/ots/src/gvar.cc new file mode 100644 index 0000000000..33e55ff5ef --- /dev/null +++ b/gfx/ots/src/gvar.cc @@ -0,0 +1,209 @@ +// 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" +#include "ots-memory-stream.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<OpenTypeFVAR*>( + 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<OpenTypeMAXP*>( + 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; +} + +#ifdef OTS_SYNTHESIZE_MISSING_GVAR +bool OpenTypeGVAR::InitEmpty() { + // Generate an empty but well-formed 'gvar' table for the font. + const ots::Font* font = GetFont(); + + OpenTypeFVAR* fvar = static_cast<OpenTypeFVAR*>(font->GetTypedTable(OTS_TAG_FVAR)); + if (!fvar) { + return DropVariations("Required fvar table missing"); + } + + OpenTypeMAXP* maxp = static_cast<OpenTypeMAXP*>(font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return DropVariations("Required maxp table missing"); + } + + uint16_t majorVersion = 1; + uint16_t minorVersion = 0; + uint16_t axisCount = fvar->AxisCount(); + uint16_t sharedTupleCount = 0; + uint32_t sharedTuplesOffset = 0; + uint16_t glyphCount = maxp->num_glyphs; + uint16_t flags = 0; + uint32_t glyphVariationDataArrayOffset = 0; + + size_t length = 6 * sizeof(uint16_t) + 2 * sizeof(uint32_t) // basic header fields + + (glyphCount + 1) * sizeof(uint16_t); // glyphVariationDataOffsets[] array + + uint8_t* data = new uint8_t[length]; + MemoryStream stream(data, length); + if (!stream.WriteU16(majorVersion) || + !stream.WriteU16(minorVersion) || + !stream.WriteU16(axisCount) || + !stream.WriteU16(sharedTupleCount) || + !stream.WriteU32(sharedTuplesOffset) || + !stream.WriteU16(glyphCount) || + !stream.WriteU16(flags) || + !stream.WriteU32(glyphVariationDataArrayOffset) || + !stream.Pad((glyphCount + 1) * sizeof(uint16_t))) { + delete[] data; + return DropVariations("Failed to generate dummy gvar table"); + } + + this->m_data = data; + this->m_length = length; + this->m_ownsData = true; + + return true; +} +#endif + +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 0000000000..a25df6b855 --- /dev/null +++ b/gfx/ots/src/gvar.h @@ -0,0 +1,43 @@ +// 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), m_ownsData(false) { } + + virtual ~OpenTypeGVAR() { + if (m_ownsData) { + delete[] m_data; + } + } + + bool Parse(const uint8_t* data, size_t length); + bool Serialize(OTSStream* out); + +#ifdef OTS_SYNTHESIZE_MISSING_GVAR + bool InitEmpty(); +#endif + + private: + const uint8_t *m_data; + size_t m_length; + + bool m_ownsData; +}; + +} // namespace ots + +#endif // OTS_GVAR_H_ diff --git a/gfx/ots/src/hdmx.cc b/gfx/ots/src/hdmx.cc new file mode 100644 index 0000000000..42ac9de8de --- /dev/null +++ b/gfx/ots/src/hdmx.cc @@ -0,0 +1,120 @@ +// 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 "hdmx.h" +#include "head.h" +#include "maxp.h" + +// hdmx - Horizontal Device Metrics +// http://www.microsoft.com/typography/otspec/hdmx.htm + +namespace ots { + +bool OpenTypeHDMX::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); + if (!head || !maxp) { + return Error("Missing maxp or head tables in font, needed by hdmx"); + } + + 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(&this->version) || + !table.ReadS16(&num_recs) || + !table.ReadS32(&this->size_device_record)) { + return Error("Failed to read table header"); + } + if (this->version != 0) { + return Drop("Unsupported version: %u", this->version); + } + if (num_recs <= 0) { + return Drop("Bad numRecords: %d", num_recs); + } + 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); + } + + 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; + 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 Error("Failed to read DeviceRecord %d", i); + } + if ((i != 0) && + (rec.pixel_size <= last_pixel_size)) { + return Drop("DeviceRecord's are not sorted"); + } + last_pixel_size = rec.pixel_size; + + rec.widths.reserve(maxp->num_glyphs); + for (unsigned j = 0; j < maxp->num_glyphs; ++j) { + uint8_t width; + if (!table.ReadU8(&width)) { + return Error("Failed to read glyph width %d in DeviceRecord %d", j, i); + } + rec.widths.push_back(width); + } + + if ((this->pad_len > 0) && + !table.Skip(this->pad_len)) { + return Error("DeviceRecord %d should be padded by %d", i, this->pad_len); + } + + this->records.push_back(rec); + } + + return true; +} + +bool OpenTypeHDMX::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; +} + +bool OpenTypeHDMX::Serialize(OTSStream *out) { + const int16_t num_recs = static_cast<int16_t>(this->records.size()); + if (this->records.size() > + static_cast<size_t>(std::numeric_limits<int16_t>::max()) || + !out->WriteU16(this->version) || + !out->WriteS16(num_recs) || + !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 = 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 Error("Failed to write DeviceRecord %d", i); + } + 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; +} + +} // namespace ots diff --git a/gfx/ots/src/hdmx.h b/gfx/ots/src/hdmx.h new file mode 100644 index 0000000000..5d323dd7e7 --- /dev/null +++ b/gfx/ots/src/hdmx.h @@ -0,0 +1,38 @@ +// 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_HDMX_H_ +#define OTS_HDMX_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeHDMXDeviceRecord { + uint8_t pixel_size; + uint8_t max_width; + std::vector<uint8_t> widths; +}; + +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; + std::vector<OpenTypeHDMXDeviceRecord> records; +}; + +} // namespace ots + +#endif diff --git a/gfx/ots/src/head.cc b/gfx/ots/src/head.cc new file mode 100644 index 0000000000..6088504c8b --- /dev/null +++ b/gfx/ots/src/head.cc @@ -0,0 +1,132 @@ +// 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 "head.h" + +#include <cstring> + +// head - Font Header +// http://www.microsoft.com/typography/otspec/head.htm + +namespace ots { + +bool OpenTypeHEAD::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + uint32_t version = 0; + if (!table.ReadU32(&version) || + !table.ReadU32(&this->revision)) { + return Error("Failed to read table header"); + } + + if (version >> 16 != 1) { + return Error("Unsupported majorVersion: %d", version >> 16); + } + + // Skip the checksum adjustment + if (!table.Skip(4)) { + return Error("Failed to read checksum"); + } + + uint32_t magic; + if (!table.ReadU32(&magic) || magic != 0x5F0F3CF5) { + return Error("Failed to read or incorrect magicNumber"); + } + + if (!table.ReadU16(&this->flags)) { + return Error("Failed to read flags"); + } + + // We allow bits 0..4, 11..13 + this->flags &= 0x381f; + + if (!table.ReadU16(&this->upem)) { + return Error("Failed to read unitsPerEm"); + } + + // upem must be in range + if (this->upem < 16 || + this->upem > 16384) { + return Error("unitsPerEm on in the range [16, 16384]: %d", this->upem); + } + + if (!table.ReadR64(&this->created) || + !table.ReadR64(&this->modified)) { + return Error("Can't read font dates"); + } + + 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 (this->xmin > this->xmax) { + return Error("Bad x dimension in the font bounding box (%d, %d)", + this->xmin, this->xmax); + } + if (this->ymin > this->ymax) { + return Error("Bad y dimension in the font bounding box (%d, %d)", + this->ymin, this->ymax); + } + + if (!table.ReadU16(&this->mac_style)) { + return Error("Failed to read macStyle"); + } + + // We allow bits 0..6 + this->mac_style &= 0x7f; + + 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 Error("Failed to read fontDirectionHint"); + } + + if (!table.ReadS16(&this->index_to_loc_format)) { + return Error("Failed to read indexToLocFormat"); + } + 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 Error("Failed to read or bad glyphDataFormat"); + } + + return true; +} + +bool OpenTypeHEAD::Serialize(OTSStream *out) { + if (!out->WriteU32(0x00010000) || + !out->WriteU32(this->revision) || + !out->WriteU32(0) || // check sum not filled in yet + !out->WriteU32(0x5F0F3CF5) || + !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(this->index_to_loc_format) || + !out->WriteS16(0)) { + return Error("Failed to write table"); + } + + return true; +} + +} // namespace diff --git a/gfx/ots/src/head.h b/gfx/ots/src/head.h new file mode 100644 index 0000000000..a040fcf248 --- /dev/null +++ b/gfx/ots/src/head.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_HEAD_H_ +#define OTS_HEAD_H_ + +#include "ots.h" + +namespace ots { + +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 upem; + uint64_t created; + uint64_t modified; + + int16_t xmin, xmax; + int16_t ymin, ymax; + + uint16_t mac_style; + uint16_t min_ppem; + int16_t index_to_loc_format; +}; + +} // namespace ots + +#endif // OTS_HEAD_H_ diff --git a/gfx/ots/src/hhea.cc b/gfx/ots/src/hhea.cc new file mode 100644 index 0000000000..55b2753bf9 --- /dev/null +++ b/gfx/ots/src/hhea.cc @@ -0,0 +1,28 @@ +// 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 "hhea.h" + +#include "head.h" +#include "maxp.h" + +// hhea - Horizontal Header +// http://www.microsoft.com/typography/otspec/hhea.htm + +namespace ots { + +bool OpenTypeHHEA::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (!table.ReadU32(&this->version)) { + return Error("Failed to read table version"); + } + if (this->version >> 16 != 1) { + return Error("Unsupported majorVersion: %d", this->version >> 16); + } + + return OpenTypeMetricsHeader::Parse(data, length); +} + +} // namespace ots diff --git a/gfx/ots/src/hhea.h b/gfx/ots/src/hhea.h new file mode 100644 index 0000000000..d4c871fa3d --- /dev/null +++ b/gfx/ots/src/hhea.h @@ -0,0 +1,23 @@ +// 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_HHEA_H_ +#define OTS_HHEA_H_ + +#include "metrics.h" +#include "ots.h" + +namespace ots { + +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); +}; + +} // namespace ots + +#endif // OTS_HHEA_H_ diff --git a/gfx/ots/src/hmtx.h b/gfx/ots/src/hmtx.h new file mode 100644 index 0000000000..4bf49349c7 --- /dev/null +++ b/gfx/ots/src/hmtx.h @@ -0,0 +1,22 @@ +// 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_HMTX_H_ +#define OTS_HMTX_H_ + +#include "metrics.h" +#include "hhea.h" +#include "ots.h" + +namespace ots { + +class OpenTypeHMTX : public OpenTypeMetricsTable { + public: + explicit OpenTypeHMTX(Font *font, uint32_t tag) + : OpenTypeMetricsTable(font, tag, tag, OTS_TAG_HHEA) { } +}; + +} // namespace ots + +#endif // OTS_HMTX_H_ diff --git a/gfx/ots/src/hvar.cc b/gfx/ots/src/hvar.cc new file mode 100644 index 0000000000..2bcea86803 --- /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 0000000000..5bfd2e4eae --- /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 new file mode 100644 index 0000000000..ec41dfb9c0 --- /dev/null +++ b/gfx/ots/src/kern.cc @@ -0,0 +1,177 @@ +// 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 "kern.h" + +// kern - Kerning +// http://www.microsoft.com/typography/otspec/kern.htm + +namespace ots { + +bool OpenTypeKERN::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + uint16_t num_tables = 0; + if (!table.ReadU16(&this->version) || + !table.ReadU16(&num_tables)) { + return Error("Failed to read table header"); + } + + if (this->version > 0) { + return Drop("Unsupported table version: %d", this->version); + } + + if (num_tables == 0) { + return Drop("nTables is zero"); + } + + 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 Error("Failed to read subtable %d header", i); + } + + if (subtable.version > 0) { + 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 Error("Bad subtable %d offset %ld", i, current_offset); + } + + if (!table.ReadU16(&subtable.coverage)) { + return Error("Failed to read subtable %d coverage", i); + } + + if (!(subtable.coverage & 0x1)) { + Warning( + "We don't support vertical data as the renderer doesn't support it."); + continue; + } + if (subtable.coverage & 0xF0) { + return Drop("Reserved fields should be zero"); + } + const uint32_t format = (subtable.coverage & 0xFF00) >> 8; + if (format != 0) { + Warning("Ignoring subtable %d with unsupported format: %d", i, format); + continue; + } + + // Parse the format 0 field. + uint16_t num_pairs = 0; + if (!table.ReadU16(&num_pairs) || + !table.ReadU16(&subtable.search_range) || + !table.ReadU16(&subtable.entry_selector) || + !table.ReadU16(&subtable.range_shift)) { + return Error("Failed to read subtable %d format 0 fields", i); + } + + if (!num_pairs) { + return Drop("Zero length subtable is found"); + } + + // Sanity checks for search_range, entry_selector, and range_shift. See the + // comment in ots.cc for details. + const size_t kFormat0PairSize = 6; // left, right, and value. 2 bytes each. + if (num_pairs > (65536 / kFormat0PairSize)) { + // Some fonts (e.g. calibri.ttf, pykes_peak_zero.ttf) have pairs >= 10923. + return Drop("Too large subtable"); + } + unsigned max_pow2 = 0; + while (1u << (max_pow2 + 1) <= num_pairs) { + ++max_pow2; + } + const uint16_t expected_search_range = (1u << max_pow2) * kFormat0PairSize; + if (subtable.search_range != expected_search_range) { + Warning("bad search range"); + subtable.search_range = expected_search_range; + } + if (subtable.entry_selector != max_pow2) { + 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) { + Warning("bad range shift"); + subtable.range_shift = expected_range_shift; + } + + // Read kerning pairs. + subtable.pairs.reserve(num_pairs); + uint32_t last_pair = 0; + for (unsigned j = 0; j < num_pairs; ++j) { + OpenTypeKERNFormat0Pair kerning_pair; + if (!table.ReadU16(&kerning_pair.left) || + !table.ReadU16(&kerning_pair.right) || + !table.ReadS16(&kerning_pair.value)) { + return 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. + return Drop("Kerning pairs are not sorted"); + } + last_pair = current_pair; + subtable.pairs.push_back(kerning_pair); + } + + this->subtables.push_back(subtable); + } + + if (!this->subtables.size()) { + return Drop("All subtables were removed"); + } + + return true; +} + +bool OpenTypeKERN::Serialize(OTSStream *out) { + const uint16_t num_subtables = static_cast<uint16_t>(this->subtables.size()); + if (num_subtables != this->subtables.size() || + !out->WriteU16(this->version) || + !out->WriteU16(num_subtables)) { + return Error("Failed to write kern table header"); + } + + for (uint16_t i = 0; i < num_subtables; ++i) { + const size_t length = 14 + (6 * this->subtables[i].pairs.size()); + if (length > std::numeric_limits<uint16_t>::max() || + !out->WriteU16(this->subtables[i].version) || + !out->WriteU16(static_cast<uint16_t>(length)) || + !out->WriteU16(this->subtables[i].coverage) || + !out->WriteU16( + static_cast<uint16_t>(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); + } + } + } + + return true; +} + +bool OpenTypeKERN::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; +} + +} // namespace ots diff --git a/gfx/ots/src/kern.h b/gfx/ots/src/kern.h new file mode 100644 index 0000000000..38f056851b --- /dev/null +++ b/gfx/ots/src/kern.h @@ -0,0 +1,49 @@ +// 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_KERN_H_ +#define OTS_KERN_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeKERNFormat0Pair { + uint16_t left; + uint16_t right; + int16_t value; +}; + +struct OpenTypeKERNFormat0 { + uint16_t version; + uint16_t coverage; + uint16_t search_range; + uint16_t entry_selector; + uint16_t range_shift; + std::vector<OpenTypeKERNFormat0Pair> pairs; +}; + +// Format 2 is not supported. Since the format is not supported by Windows, +// WebFonts unlikely use it. I've checked thousands of proprietary fonts and +// free fonts, and found no font uses the format. + +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<OpenTypeKERNFormat0> subtables; +}; + +} // namespace ots + +#endif // OTS_KERN_H_ diff --git a/gfx/ots/src/layout.cc b/gfx/ots/src/layout.cc new file mode 100644 index 0000000000..4168ac63b4 --- /dev/null +++ b/gfx/ots/src/layout.cc @@ -0,0 +1,1735 @@ +// 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 "layout.h" + +#include <limits> +#include <vector> + +#include "fvar.h" +#include "gdef.h" +#include "maxp.h" + +// OpenType Layout Common Table Formats +// http://www.microsoft.com/typography/otspec/chapter2.htm + +#define TABLE_NAME "Layout" // XXX: use individual table names + +namespace { + +// The 'DFLT' tag of script table. +const uint32_t kScriptTableTagDflt = 0x44464c54; +// The value which represents there is no required feature index. +const uint16_t kNoRequiredFeatureIndexDefined = 0xFFFF; +// The lookup flag bit which indicates existence of MarkFilteringSet. +const uint16_t kUseMarkFilteringSetBit = 0x0010; +// The maximum type number of format for device tables. +const uint16_t kMaxDeltaFormatType = 3; +// 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; + uint16_t offset; +}; + +struct LangSysRecord { + uint32_t tag; + uint16_t offset; +}; + +struct FeatureRecord { + uint32_t tag; + uint16_t offset; +}; + +bool ParseLangSysTable(const ots::Font *font, + ots::Buffer *subtable, const uint32_t tag, + const uint16_t num_features) { + uint16_t offset_lookup_order = 0; + uint16_t req_feature_index = 0; + uint16_t feature_count = 0; + if (!subtable->ReadU16(&offset_lookup_order) || + !subtable->ReadU16(&req_feature_index) || + !subtable->ReadU16(&feature_count)) { + return OTS_FAILURE_MSG("Failed to read langsys header for tag %c%c%c%c", OTS_UNTAG(tag)); + } + // |offset_lookup_order| is reserved and should be NULL. + if (offset_lookup_order != 0) { + return OTS_FAILURE_MSG("Bad lookup offset order %d for langsys tag %c%c%c%c", offset_lookup_order, OTS_UNTAG(tag)); + } + if (req_feature_index != kNoRequiredFeatureIndexDefined && + req_feature_index >= num_features) { + return OTS_FAILURE_MSG("Bad required features index %d for langsys tag %c%c%c%c", req_feature_index, OTS_UNTAG(tag)); + } + if (feature_count > num_features) { + return OTS_FAILURE_MSG("Bad feature count %d for langsys tag %c%c%c%c", feature_count, OTS_UNTAG(tag)); + } + + for (unsigned i = 0; i < feature_count; ++i) { + uint16_t feature_index = 0; + if (!subtable->ReadU16(&feature_index)) { + return OTS_FAILURE_MSG("Failed to read feature index %d for langsys tag %c%c%c%c", i, OTS_UNTAG(tag)); + } + if (feature_index >= num_features) { + return OTS_FAILURE_MSG("Bad feature index %d for feature %d for langsys tag %c%c%c%c", feature_index, i, OTS_UNTAG(tag)); + } + } + return true; +} + +bool ParseScriptTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint32_t tag, const uint16_t num_features) { + ots::Buffer subtable(data, length); + + uint16_t offset_default_lang_sys = 0; + uint16_t lang_sys_count = 0; + if (!subtable.ReadU16(&offset_default_lang_sys) || + !subtable.ReadU16(&lang_sys_count)) { + return OTS_FAILURE_MSG("Failed to read script header for script tag %c%c%c%c", OTS_UNTAG(tag)); + } + + // The spec requires a script table for 'DFLT' tag must contain non-NULL + // |offset_default_lang_sys|. + // 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"); + } + } + + const unsigned lang_sys_record_end = + 6 * static_cast<unsigned>(lang_sys_count) + 4; + if (lang_sys_record_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of langsys record %d for script tag %c%c%c%c", lang_sys_record_end, OTS_UNTAG(tag)); + } + + std::vector<LangSysRecord> lang_sys_records; + lang_sys_records.resize(lang_sys_count); + uint32_t last_tag = 0; + for (unsigned i = 0; i < lang_sys_count; ++i) { + if (!subtable.ReadU32(&lang_sys_records[i].tag) || + !subtable.ReadU16(&lang_sys_records[i].offset)) { + return OTS_FAILURE_MSG("Failed to read langsys record header %d for script tag %c%c%c%c", i, OTS_UNTAG(tag)); + } + // The record array must store the records alphabetically by tag + if (last_tag != 0 && last_tag > lang_sys_records[i].tag) { + return OTS_FAILURE_MSG("Bad last tag %d for langsys record %d for script tag %c%c%c%c", last_tag, i, OTS_UNTAG(tag)); + } + if (lang_sys_records[i].offset < lang_sys_record_end || + lang_sys_records[i].offset >= length) { + return OTS_FAILURE_MSG("bad offset to lang sys table: %x", + lang_sys_records[i].offset); + } + last_tag = lang_sys_records[i].tag; + } + + // Check lang sys tables + for (unsigned i = 0; i < lang_sys_count; ++i) { + subtable.set_offset(lang_sys_records[i].offset); + if (!ParseLangSysTable(font, &subtable, lang_sys_records[i].tag, num_features)) { + return OTS_FAILURE_MSG("Failed to parse langsys table %d (%c%c%c%c) for script tag %c%c%c%c", i, OTS_UNTAG(lang_sys_records[i].tag), OTS_UNTAG(tag)); + } + } + + return true; +} + +bool ParseFeatureTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_feature_params = 0; + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&offset_feature_params) || + !subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read feature table header"); + } + + const unsigned feature_table_end = + 2 * static_cast<unsigned>(lookup_count) + 4; + if (feature_table_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of feature table %d", feature_table_end); + } + // |offset_feature_params| is generally set to NULL. + if (offset_feature_params != 0 && + (offset_feature_params < feature_table_end || + offset_feature_params >= length)) { + return OTS_FAILURE_MSG("Bad feature params offset %d", offset_feature_params); + } + + for (unsigned i = 0; i < lookup_count; ++i) { + uint16_t lookup_index = 0; + if (!subtable.ReadU16(&lookup_index)) { + return OTS_FAILURE_MSG("Failed to read lookup index for lookup %d", i); + } + // lookup index starts with 0. + if (lookup_index >= num_lookups) { + return OTS_FAILURE_MSG("Bad lookup index %d for lookup %d", lookup_index, i); + } + } + return true; +} + +bool ParseClassDefFormat1(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t num_classes) { + ots::Buffer subtable(data, length); + + // Skip format field. + if (!subtable.Skip(2)) { + return OTS_FAILURE_MSG("Failed to skip class definition header"); + } + + uint16_t start_glyph = 0; + if (!subtable.ReadU16(&start_glyph)) { + return OTS_FAILURE_MSG("Failed to read starting glyph of class definition"); + } + if (start_glyph > num_glyphs) { + return OTS_FAILURE_MSG("Bad starting glyph %d in class definition", start_glyph); + } + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read glyph count in class definition"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("bad glyph count: %u", glyph_count); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t class_value = 0; + if (!subtable.ReadU16(&class_value)) { + return OTS_FAILURE_MSG("Failed to read class value for glyph %d in class definition", i); + } + if (class_value > num_classes) { + return OTS_FAILURE_MSG("Bad class value %d for glyph %d in class definition", class_value, i); + } + } + + return true; +} + +bool ParseClassDefFormat2(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t num_classes) { + ots::Buffer subtable(data, length); + + // Skip format field. + if (!subtable.Skip(2)) { + return OTS_FAILURE_MSG("Failed to read class definition format"); + } + + uint16_t range_count = 0; + if (!subtable.ReadU16(&range_count)) { + return OTS_FAILURE_MSG("Failed to read classRangeCount"); + } + if (range_count > num_glyphs) { + return OTS_FAILURE_MSG("classRangeCount > glyph count: %u > %u", range_count, num_glyphs); + } + + uint16_t last_end = 0; + for (unsigned i = 0; i < range_count; ++i) { + uint16_t start = 0; + uint16_t end = 0; + uint16_t class_value = 0; + if (!subtable.ReadU16(&start) || + !subtable.ReadU16(&end) || + !subtable.ReadU16(&class_value)) { + return OTS_FAILURE_MSG("Failed to read ClassRangeRecord %d", i); + } + if (start > end) { + return OTS_FAILURE_MSG("ClassRangeRecord %d, start > end: %u > %u", i, start, end); + } + 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("ClassRangeRecord %d class > number of classes: %u > %u", i, class_value, num_classes); + } + last_end = end; + } + + return true; +} + +bool ParseCoverageFormat1(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t expected_num_glyphs) { + ots::Buffer subtable(data, length); + + // Skip format field. + if (!subtable.Skip(2)) { + return OTS_FAILURE_MSG("Failed to skip coverage format"); + } + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read glyph count in coverage"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("bad glyph count: %u", glyph_count); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t glyph = 0; + if (!subtable.ReadU16(&glyph)) { + return OTS_FAILURE_MSG("Failed to read glyph %d in coverage", i); + } + if (glyph > num_glyphs) { + return OTS_FAILURE_MSG("bad glyph ID: %u", glyph); + } + } + + if (expected_num_glyphs && expected_num_glyphs != glyph_count) { + return OTS_FAILURE_MSG("unexpected number of glyphs: %u", glyph_count); + } + + return true; +} + +bool ParseCoverageFormat2(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t expected_num_glyphs) { + ots::Buffer subtable(data, length); + + // Skip format field. + if (!subtable.Skip(2)) { + return OTS_FAILURE_MSG("Failed to skip format of coverage type 2"); + } + + uint16_t range_count = 0; + if (!subtable.ReadU16(&range_count)) { + return OTS_FAILURE_MSG("Failed to read range count in coverage"); + } + if (range_count > num_glyphs) { + return OTS_FAILURE_MSG("bad range count: %u", range_count); + } + uint16_t last_end = 0; + uint16_t last_start_coverage_index = 0; + for (unsigned i = 0; i < range_count; ++i) { + uint16_t start = 0; + uint16_t end = 0; + uint16_t start_coverage_index = 0; + if (!subtable.ReadU16(&start) || + !subtable.ReadU16(&end) || + !subtable.ReadU16(&start_coverage_index)) { + return OTS_FAILURE_MSG("Failed to read range %d in coverage", i); + } + + // Some of the Adobe Pro fonts have ranges that overlap by one element: the + // start of one range is equal to the end of the previous range. Therefore + // the < in the following condition should be <= were it not for this. + // See crbug.com/134135. + if (start > end || (last_end && start < last_end)) { + return OTS_FAILURE_MSG("glyph range is overlapping."); + } + if (start_coverage_index != last_start_coverage_index) { + return OTS_FAILURE_MSG("bad start coverage index."); + } + last_end = end; + last_start_coverage_index += end - start + 1; + } + + if (expected_num_glyphs && + expected_num_glyphs != last_start_coverage_index) { + return OTS_FAILURE_MSG("unexpected number of glyphs: %u", last_start_coverage_index); + } + + return true; +} + +// Parsers for Contextual subtables in GSUB/GPOS tables. + +bool ParseLookupRecord(const ots::Font *font, + ots::Buffer *subtable, const uint16_t num_glyphs, + const uint16_t num_lookups) { + uint16_t sequence_index = 0; + uint16_t lookup_list_index = 0; + if (!subtable->ReadU16(&sequence_index) || + !subtable->ReadU16(&lookup_list_index)) { + return OTS_FAILURE_MSG("Failed to read header for lookup record"); + } + if (sequence_index >= num_glyphs) { + return OTS_FAILURE_MSG("Bad sequence index %d in lookup record", sequence_index); + } + if (lookup_list_index >= num_lookups) { + return OTS_FAILURE_MSG("Bad lookup list index %d in lookup record", lookup_list_index); + } + return true; +} + +bool ParseRuleSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&glyph_count) || + !subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read rule subtable header"); + } + + if (glyph_count == 0) { + return OTS_FAILURE_MSG("Bad glyph count %d in rule subtable", glyph_count); + } + for (unsigned i = 0; i < glyph_count - static_cast<unsigned>(1); ++i) { + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read glyph %d", i); + } + if (glyph_id > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph %d for entry %d", glyph_id, i); + } + } + + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d", i); + } + } + return true; +} + +bool ParseRuleSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t rule_count = 0; + if (!subtable.ReadU16(&rule_count)) { + return OTS_FAILURE_MSG("Failed to read rule count in rule set"); + } + const unsigned rule_end = 2 * static_cast<unsigned>(rule_count) + 2; + if (rule_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of rule %d in rule set", rule_end); + } + + for (unsigned i = 0; i < rule_count; ++i) { + uint16_t offset_rule = 0; + if (!subtable.ReadU16(&offset_rule)) { + return OTS_FAILURE_MSG("Failed to read rule offset for rule set %d", i); + } + if (offset_rule < rule_end || offset_rule >= length) { + return OTS_FAILURE_MSG("Bad rule offset %d in set %d", offset_rule, i); + } + if (!ParseRuleSubtable(font, data + offset_rule, length - offset_rule, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse rule set %d", i); + } + } + + return true; +} + +bool ParseContextFormat1(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_coverage = 0; + uint16_t rule_set_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&rule_set_count)) { + return OTS_FAILURE_MSG("Failed to read header of context format 1"); + } + + const unsigned rule_set_end = static_cast<unsigned>(6) + + rule_set_count * 2; + if (rule_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of rule set %d of context format 1", rule_set_end); + } + if (offset_coverage < rule_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in context format 1", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table in context format 1"); + } + + for (unsigned i = 0; i < rule_set_count; ++i) { + uint16_t offset_rule = 0; + if (!subtable.ReadU16(&offset_rule)) { + return OTS_FAILURE_MSG("Failed to read rule offset %d in context format 1", i); + } + if (offset_rule < rule_set_end || offset_rule >= length) { + return OTS_FAILURE_MSG("Bad rule offset %d in rule %d in context format 1", offset_rule, i); + } + if (!ParseRuleSetTable(font, data + offset_rule, length - offset_rule, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse rule set %d in context format 1", i); + } + } + + return true; +} + +bool ParseClassRuleTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&glyph_count) || + !subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read header of class rule table"); + } + + if (glyph_count == 0 || glyph_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count %d in class rule table", glyph_count); + } + + // ClassRule table contains an array of classes. Each value of classes + // could take arbitrary values including zero so we don't check these value. + const unsigned num_classes = glyph_count - static_cast<unsigned>(1); + if (!subtable.Skip(2 * num_classes)) { + return OTS_FAILURE_MSG("Failed to skip classes in class rule table"); + } + + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d in class rule table", i); + } + } + return true; +} + +bool ParseClassSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t class_rule_count = 0; + if (!subtable.ReadU16(&class_rule_count)) { + return OTS_FAILURE_MSG("Failed to read class rule count in class set table"); + } + const unsigned class_rule_end = + 2 * static_cast<unsigned>(class_rule_count) + 2; + if (class_rule_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("bad class rule end %d in class set table", class_rule_end); + } + for (unsigned i = 0; i < class_rule_count; ++i) { + uint16_t offset_class_rule = 0; + if (!subtable.ReadU16(&offset_class_rule)) { + return OTS_FAILURE_MSG("Failed to read class rule offset %d in class set table", i); + } + if (offset_class_rule < class_rule_end || offset_class_rule >= length) { + return OTS_FAILURE_MSG("Bad class rule offset %d in class %d", offset_class_rule, i); + } + if (!ParseClassRuleTable(font, data + offset_class_rule, + length - offset_class_rule, num_glyphs, + num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse class rule table %d", i); + } + } + + return true; +} + +bool ParseContextFormat2(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_coverage = 0; + uint16_t offset_class_def = 0; + uint16_t class_set_cnt = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&offset_class_def) || + !subtable.ReadU16(&class_set_cnt)) { + return OTS_FAILURE_MSG("Failed to read header for context format 2"); + } + + const unsigned class_set_end = 2 * static_cast<unsigned>(class_set_cnt) + 8; + if (class_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of class set %d for context format 2", class_set_end); + } + if (offset_coverage < class_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in context format 2", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table in context format 2"); + } + + if (offset_class_def < class_set_end || offset_class_def >= length) { + return OTS_FAILURE_MSG("bad class definition offset %d in context format 2", offset_class_def); + } + if (!ots::ParseClassDefTable(font, data + offset_class_def, + length - offset_class_def, + num_glyphs, ots::kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse class definition table in context format 2"); + } + + for (unsigned i = 0; i < class_set_cnt; ++i) { + uint16_t offset_class_rule = 0; + if (!subtable.ReadU16(&offset_class_rule)) { + return OTS_FAILURE_MSG("Failed to read class rule offset %d in context format 2", i); + } + if (offset_class_rule) { + if (offset_class_rule < class_set_end || offset_class_rule >= length) { + return OTS_FAILURE_MSG("Bad class rule offset %d for rule %d in context format 2", offset_class_rule, i); + } + if (!ParseClassSetTable(font, data + offset_class_rule, + length - offset_class_rule, num_glyphs, + num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse class set %d in context format 2", i); + } + } + } + + return true; +} + +bool ParseContextFormat3(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + uint16_t lookup_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&glyph_count) || + !subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read header in context format 3"); + } + + if (glyph_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count %d in context format 3", glyph_count); + } + const unsigned lookup_record_end = 2 * static_cast<unsigned>(glyph_count) + + 4 * static_cast<unsigned>(lookup_count) + 6; + if (lookup_record_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of lookup %d in context format 3", lookup_record_end); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t offset_coverage = 0; + if (!subtable.ReadU16(&offset_coverage)) { + return OTS_FAILURE_MSG("Failed to read coverage offset %d in conxtext format 3", i); + } + if (offset_coverage < lookup_record_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d for glyph %d in context format 3", offset_coverage, i); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table for glyph %d in context format 3", i); + } + } + + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d in context format 3", i); + } + } + + return true; +} + +// Parsers for Chaning Contextual subtables in GSUB/GPOS tables. + +bool ParseChainRuleSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t backtrack_count = 0; + if (!subtable.ReadU16(&backtrack_count)) { + return OTS_FAILURE_MSG("Failed to read backtrack count in chain rule subtable"); + } + for (unsigned i = 0; i < backtrack_count; ++i) { + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read backtrack glyph %d in chain rule subtable", i); + } + if (glyph_id > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph id %d for bactrack glyph %d in chain rule subtable", glyph_id, i); + } + } + + uint16_t input_count = 0; + if (!subtable.ReadU16(&input_count)) { + return OTS_FAILURE_MSG("Failed to read input count in chain rule subtable"); + } + if (input_count == 0) { + return OTS_FAILURE_MSG("Bad input count %d in chain rule subtable", input_count); + } + for (unsigned i = 0; i < input_count - static_cast<unsigned>(1); ++i) { + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read input glyph %d in chain rule subtable", i); + } + if (glyph_id > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph id %d for input glyph %d in chain rule subtable", glyph_id, i); + } + } + + uint16_t lookahead_count = 0; + if (!subtable.ReadU16(&lookahead_count)) { + return OTS_FAILURE_MSG("Failed to read lookahead count in chain rule subtable"); + } + for (unsigned i = 0; i < lookahead_count; ++i) { + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read lookahead glyph %d in chain rule subtable", i); + } + if (glyph_id > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph id %d for lookadhead glyph %d in chain rule subtable", glyph_id, i); + } + } + + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read lookup count in chain rule subtable"); + } + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d in chain rule subtable", i); + } + } + + return true; +} + +bool ParseChainRuleSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t chain_rule_count = 0; + if (!subtable.ReadU16(&chain_rule_count)) { + return OTS_FAILURE_MSG("Failed to read rule count in chain rule set"); + } + const unsigned chain_rule_end = + 2 * static_cast<unsigned>(chain_rule_count) + 2; + if (chain_rule_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of chain rule %d in chain rule set", chain_rule_end); + } + for (unsigned i = 0; i < chain_rule_count; ++i) { + uint16_t offset_chain_rule = 0; + if (!subtable.ReadU16(&offset_chain_rule)) { + return OTS_FAILURE_MSG("Failed to read chain rule offset %d in chain rule set", i); + } + if (offset_chain_rule < chain_rule_end || offset_chain_rule >= length) { + return OTS_FAILURE_MSG("Bad chain rule offset %d for chain rule %d in chain rule set", offset_chain_rule, i); + } + if (!ParseChainRuleSubtable(font, data + offset_chain_rule, + length - offset_chain_rule, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chain rule %d in chain rule set", i); + } + } + + return true; +} + +bool ParseChainContextFormat1(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_coverage = 0; + uint16_t chain_rule_set_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&chain_rule_set_count)) { + return OTS_FAILURE_MSG("Failed to read header of chain context format 1"); + } + + const unsigned chain_rule_set_end = + 2 * static_cast<unsigned>(chain_rule_set_count) + 6; + if (chain_rule_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad chain rule end %d in chain context format 1", chain_rule_set_end); + } + if (offset_coverage < chain_rule_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in chain context format 1", chain_rule_set_end); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table for chain context format 1"); + } + + for (unsigned i = 0; i < chain_rule_set_count; ++i) { + uint16_t offset_chain_rule_set = 0; + if (!subtable.ReadU16(&offset_chain_rule_set)) { + return OTS_FAILURE_MSG("Failed to read chain rule offset %d in chain context format 1", i); + } + if (offset_chain_rule_set < chain_rule_set_end || + offset_chain_rule_set >= length) { + return OTS_FAILURE_MSG("Bad chain rule set offset %d for chain rule set %d in chain context format 1", offset_chain_rule_set, i); + } + if (!ParseChainRuleSetTable(font, data + offset_chain_rule_set, + length - offset_chain_rule_set, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chain rule set %d in chain context format 1", i); + } + } + + return true; +} + +bool ParseChainClassRuleSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + // In this subtable, we don't check the value of classes for now since + // these could take arbitrary values. + + uint16_t backtrack_count = 0; + if (!subtable.ReadU16(&backtrack_count)) { + return OTS_FAILURE_MSG("Failed to read backtrack count in chain class rule subtable"); + } + if (!subtable.Skip(2 * backtrack_count)) { + return OTS_FAILURE_MSG("Failed to skip backtrack offsets in chain class rule subtable"); + } + + uint16_t input_count = 0; + if (!subtable.ReadU16(&input_count)) { + return OTS_FAILURE_MSG("Failed to read input count in chain class rule subtable"); + } + if (input_count == 0) { + return OTS_FAILURE_MSG("Bad input count %d in chain class rule subtable", input_count); + } + if (!subtable.Skip(2 * (input_count - 1))) { + return OTS_FAILURE_MSG("Failed to skip input offsets in chain class rule subtable"); + } + + uint16_t lookahead_count = 0; + if (!subtable.ReadU16(&lookahead_count)) { + return OTS_FAILURE_MSG("Failed to read lookahead count in chain class rule subtable"); + } + if (!subtable.Skip(2 * lookahead_count)) { + return OTS_FAILURE_MSG("Failed to skip lookahead offsets in chain class rule subtable"); + } + + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read lookup count in chain class rule subtable"); + } + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d in chain class rule subtable", i); + } + } + + return true; +} + +bool ParseChainClassSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t chain_class_rule_count = 0; + if (!subtable.ReadU16(&chain_class_rule_count)) { + return OTS_FAILURE_MSG("Failed to read rule count in chain class set"); + } + const unsigned chain_class_rule_end = + 2 * static_cast<unsigned>(chain_class_rule_count) + 2; + if (chain_class_rule_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of chain class set %d in chain class set", chain_class_rule_end); + } + for (unsigned i = 0; i < chain_class_rule_count; ++i) { + uint16_t offset_chain_class_rule = 0; + if (!subtable.ReadU16(&offset_chain_class_rule)) { + return OTS_FAILURE_MSG("Failed to read chain class rule offset %d in chain class set", i); + } + if (offset_chain_class_rule < chain_class_rule_end || + offset_chain_class_rule >= length) { + return OTS_FAILURE_MSG("Bad chain class rule offset %d for chain class %d in chain class set", offset_chain_class_rule, i); + } + if (!ParseChainClassRuleSubtable(font, data + offset_chain_class_rule, + length - offset_chain_class_rule, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chain class rule %d in chain class set", i); + } + } + + return true; +} + +bool ParseChainContextFormat2(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_coverage = 0; + uint16_t offset_backtrack_class_def = 0; + uint16_t offset_input_class_def = 0; + uint16_t offset_lookahead_class_def = 0; + uint16_t chain_class_set_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&offset_backtrack_class_def) || + !subtable.ReadU16(&offset_input_class_def) || + !subtable.ReadU16(&offset_lookahead_class_def) || + !subtable.ReadU16(&chain_class_set_count)) { + return OTS_FAILURE_MSG("Failed to read header of chain context format 2"); + } + + const unsigned chain_class_set_end = + 2 * static_cast<unsigned>(chain_class_set_count) + 12; + if (chain_class_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad chain class set end %d in chain context format 2", chain_class_set_end); + } + if (offset_coverage < chain_class_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in chain context format 2", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table in chain context format 2"); + } + + // Classes for backtrack/lookahead sequences might not be defined. + if (offset_backtrack_class_def) { + if (offset_backtrack_class_def < chain_class_set_end || + offset_backtrack_class_def >= length) { + return OTS_FAILURE_MSG("Bad backtrack class offset %d in chain context format 2", offset_backtrack_class_def); + } + if (!ots::ParseClassDefTable(font, data + offset_backtrack_class_def, + length - offset_backtrack_class_def, + num_glyphs, ots::kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse backtrack class defn table in chain context format 2"); + } + } + + if (offset_input_class_def < chain_class_set_end || + offset_input_class_def >= length) { + return OTS_FAILURE_MSG("Bad input class defn offset %d in chain context format 2", offset_input_class_def); + } + if (!ots::ParseClassDefTable(font, data + offset_input_class_def, + length - offset_input_class_def, + num_glyphs, ots::kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse input class defn in chain context format 2"); + } + + if (offset_lookahead_class_def) { + if (offset_lookahead_class_def < chain_class_set_end || + offset_lookahead_class_def >= length) { + return OTS_FAILURE_MSG("Bad lookahead class defn offset %d in chain context format 2", offset_lookahead_class_def); + } + if (!ots::ParseClassDefTable(font, data + offset_lookahead_class_def, + length - offset_lookahead_class_def, + num_glyphs, ots::kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse lookahead class defn in chain context format 2"); + } + } + + for (unsigned i = 0; i < chain_class_set_count; ++i) { + uint16_t offset_chain_class_set = 0; + if (!subtable.ReadU16(&offset_chain_class_set)) { + return OTS_FAILURE_MSG("Failed to read chain class set offset %d", i); + } + // |offset_chain_class_set| could be NULL. + if (offset_chain_class_set) { + if (offset_chain_class_set < chain_class_set_end || + offset_chain_class_set >= length) { + return OTS_FAILURE_MSG("Bad chain set class offset %d for chain set %d in chain context format 2", offset_chain_class_set, i); + } + if (!ParseChainClassSetTable(font, data + offset_chain_class_set, + length - offset_chain_class_set, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chain class set table %d in chain context format 2", i); + } + } + } + + return true; +} + +bool ParseChainContextFormat3(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t backtrack_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&backtrack_count)) { + return OTS_FAILURE_MSG("Failed to read backtrack count in chain context format 3"); + } + + std::vector<uint16_t> offsets_backtrack; + offsets_backtrack.reserve(backtrack_count); + for (unsigned i = 0; i < backtrack_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read backtrack offset %d in chain context format 3", i); + } + offsets_backtrack.push_back(offset); + } + if (offsets_backtrack.size() != backtrack_count) { + return OTS_FAILURE_MSG("Bad backtrack offsets size %ld in chain context format 3", offsets_backtrack.size()); + } + + uint16_t input_count = 0; + if (!subtable.ReadU16(&input_count)) { + return OTS_FAILURE_MSG("Failed to read input count in chain context format 3"); + } + std::vector<uint16_t> offsets_input; + offsets_input.reserve(input_count); + for (unsigned i = 0; i < input_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read input offset %d in chain context format 3", i); + } + offsets_input.push_back(offset); + } + if (offsets_input.size() != input_count) { + return OTS_FAILURE_MSG("Bad input offsets size %ld in chain context format 3", offsets_input.size()); + } + + uint16_t lookahead_count = 0; + if (!subtable.ReadU16(&lookahead_count)) { + return OTS_FAILURE_MSG("Failed ot read lookahead count in chain context format 3"); + } + std::vector<uint16_t> offsets_lookahead; + offsets_lookahead.reserve(lookahead_count); + for (unsigned i = 0; i < lookahead_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read lookahead offset %d in chain context format 3", i); + } + offsets_lookahead.push_back(offset); + } + if (offsets_lookahead.size() != lookahead_count) { + return OTS_FAILURE_MSG("Bad lookahead offsets size %ld in chain context format 3", offsets_lookahead.size()); + } + + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read lookup count in chain context format 3"); + } + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup %d in chain context format 3", i); + } + } + + const unsigned lookup_record_end = + 2 * (static_cast<unsigned>(backtrack_count) + + static_cast<unsigned>(input_count) + + static_cast<unsigned>(lookahead_count)) + + 4 * static_cast<unsigned>(lookup_count) + 10; + if (lookup_record_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of lookup record %d in chain context format 3", lookup_record_end); + } + for (unsigned i = 0; i < backtrack_count; ++i) { + if (offsets_backtrack[i] < lookup_record_end || + offsets_backtrack[i] >= length) { + return OTS_FAILURE_MSG("Bad backtrack offset of %d for backtrack %d in chain context format 3", offsets_backtrack[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_backtrack[i], + length - offsets_backtrack[i], num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse backtrack coverage %d in chain context format 3", i); + } + } + for (unsigned i = 0; i < input_count; ++i) { + if (offsets_input[i] < lookup_record_end || offsets_input[i] >= length) { + return OTS_FAILURE_MSG("Bad input offset %d for input %d in chain context format 3", offsets_input[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_input[i], + length - offsets_input[i], num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse input coverage table %d in chain context format 3", i); + } + } + for (unsigned i = 0; i < lookahead_count; ++i) { + if (offsets_lookahead[i] < lookup_record_end || + offsets_lookahead[i] >= length) { + return OTS_FAILURE_MSG("Bad lookadhead offset %d for lookahead %d in chain context format 3", offsets_lookahead[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_lookahead[i], + length - offsets_lookahead[i], num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse lookahead coverage table %d in chain context format 3", i); + } + } + + return true; +} + +bool ParseFeatureTableSubstitutionTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_lookups) { + ots::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 ParseConditionTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t axis_count) { + ots::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 ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t axis_count) { + ots::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; +} + +} // namespace + +namespace ots { + +// Parsing ScriptListTable requires number of features so we need to +// parse FeatureListTable before calling this function. +bool OpenTypeLayoutTable::ParseScriptListTable(const uint8_t *data, const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + uint16_t script_count = 0; + if (!subtable.ReadU16(&script_count)) { + return Error("Failed to read script count in script list table"); + } + + const unsigned script_record_end = + 6 * static_cast<unsigned>(script_count) + 2; + if (script_record_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad end of script record %d in script list table", script_record_end); + } + std::vector<ScriptRecord> script_list; + script_list.reserve(script_count); + uint32_t last_tag = 0; + for (unsigned i = 0; i < script_count; ++i) { + ScriptRecord record; + if (!subtable.ReadU32(&record.tag) || + !subtable.ReadU16(&record.offset)) { + return Error("Failed to read script record %d in script list table", i); + } + // Script tags should be arranged alphabetically by tag + if (last_tag != 0 && last_tag > record.tag) { + // Several fonts don't arrange tags alphabetically. + // It seems that the order of tags might not be a security issue + // so we just warn it. + OTS_WARNING("tags aren't arranged alphabetically."); + } + last_tag = record.tag; + if (record.offset < script_record_end || record.offset >= length) { + return Error("Bad record offset %d for script %c%c%c%c entry %d in script list table", record.offset, OTS_UNTAG(record.tag), i); + } + script_list.push_back(record); + } + if (script_list.size() != script_count) { + return Error("Bad script list size %ld in script list table", script_list.size()); + } + + // Check script records. + for (unsigned i = 0; i < script_count; ++i) { + if (!ParseScriptTable(font, data + script_list[i].offset, + length - script_list[i].offset, + script_list[i].tag, m_num_features)) { + return Error("Failed to parse script table %d", i); + } + } + + return true; +} + +// Parsing FeatureListTable requires number of lookups so we need to parse +// LookupListTable before calling this function. +bool OpenTypeLayoutTable::ParseFeatureListTable(const uint8_t *data, const size_t length) { + Font *font = GetFont(); + Buffer subtable(data, length); + + uint16_t feature_count = 0; + if (!subtable.ReadU16(&feature_count)) { + return Error("Failed to read feature count"); + } + + std::vector<FeatureRecord> feature_records; + feature_records.resize(feature_count); + const unsigned feature_record_end = + 6 * static_cast<unsigned>(feature_count) + 2; + if (feature_record_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad end of feature record %d", feature_record_end); + } + uint32_t last_tag = 0; + for (unsigned i = 0; i < feature_count; ++i) { + if (!subtable.ReadU32(&feature_records[i].tag) || + !subtable.ReadU16(&feature_records[i].offset)) { + return Error("Failed to read feature header %d", i); + } + // Feature record array should be arranged alphabetically by tag + if (last_tag != 0 && last_tag > feature_records[i].tag) { + // Several fonts don't arrange tags alphabetically. + // It seems that the order of tags might not be a security issue + // so we just warn it. + OTS_WARNING("tags aren't arranged alphabetically."); + } + last_tag = feature_records[i].tag; + if (feature_records[i].offset < feature_record_end || + feature_records[i].offset >= length) { + return Error("Bad feature offset %d for feature %d %c%c%c%c", feature_records[i].offset, i, OTS_UNTAG(feature_records[i].tag)); + } + } + + for (unsigned i = 0; i < feature_count; ++i) { + if (!ParseFeatureTable(font, data + feature_records[i].offset, + length - feature_records[i].offset, m_num_lookups)) { + return Error("Failed to parse feature table %d", i); + } + } + m_num_features = feature_count; + return true; +} + +bool OpenTypeLayoutTable::ParseLookupTable(const uint8_t *data, + const size_t length) { + Font* font = GetFont(); + Buffer subtable(data, length); + + uint16_t lookup_type = 0; + uint16_t lookup_flag = 0; + uint16_t subtable_count = 0; + if (!subtable.ReadU16(&lookup_type) || + !subtable.ReadU16(&lookup_flag) || + !subtable.ReadU16(&subtable_count)) { + return Error("Failed to read lookup table header"); + } + + if (!ValidLookupSubtableType(lookup_type)) { + return Error("Bad lookup type %d", lookup_type); + } + + bool use_mark_filtering_set = lookup_flag & kUseMarkFilteringSetBit; + + std::vector<uint16_t> subtables; + subtables.reserve(subtable_count); + // If the |kUseMarkFilteringSetBit| of |lookup_flag| is set, + // extra 2 bytes will follow after subtable offset array. + const unsigned lookup_table_end = 2 * static_cast<unsigned>(subtable_count) + + (use_mark_filtering_set ? 8 : 6); + if (lookup_table_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad end of lookup %d", lookup_table_end); + } + for (unsigned i = 0; i < subtable_count; ++i) { + uint16_t offset_subtable = 0; + if (!subtable.ReadU16(&offset_subtable)) { + return Error("Failed to read subtable offset %d", i); + } + if (offset_subtable < lookup_table_end || + offset_subtable >= length) { + return Error("Bad subtable offset %d for subtable %d", offset_subtable, i); + } + subtables.push_back(offset_subtable); + } + if (subtables.size() != subtable_count) { + return Error("Bad subtable size %ld", subtables.size()); + } + + if (use_mark_filtering_set) { + uint16_t mark_filtering_set = 0; + if (!subtable.ReadU16(&mark_filtering_set)) { + return Error("Failed to read mark filtering set"); + } + + OpenTypeGDEF *gdef = static_cast<OpenTypeGDEF*>( + font->GetTypedTable(OTS_TAG_GDEF)); + + if (gdef && (gdef->num_mark_glyph_sets == 0 || + mark_filtering_set >= gdef->num_mark_glyph_sets)) { + return Error("Bad mark filtering set %d", mark_filtering_set); + } + } + + // Parse lookup subtables for this lookup type. + for (unsigned i = 0; i < subtable_count; ++i) { + if (!ParseLookupSubtable(data + subtables[i], length - subtables[i], + lookup_type)) { + return Error("Failed to parse subtable %d", i); + } + } + return true; +} + +// For parsing GPOS/GSUB tables, this function should be called at first to +// obtain the number of lookups because parsing FeatureTableList requires +// the number. +bool OpenTypeLayoutTable::ParseLookupListTable(const uint8_t *data, + const size_t length) { + Buffer subtable(data, length); + + if (!subtable.ReadU16(&m_num_lookups)) { + return Error("Failed to read number of lookups"); + } + + std::vector<uint16_t> lookups; + lookups.reserve(m_num_lookups); + const unsigned lookup_end = + 2 * static_cast<unsigned>(m_num_lookups) + 2; + if (lookup_end > std::numeric_limits<uint16_t>::max()) { + return Error("Bad end of lookups %d", lookup_end); + } + for (unsigned i = 0; i < m_num_lookups; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return Error("Failed to read lookup offset %d", i); + } + if (offset < lookup_end || offset >= length) { + return Error("Bad lookup offset %d for lookup %d", offset, i); + } + lookups.push_back(offset); + } + if (lookups.size() != m_num_lookups) { + return Error("Bad lookup offsets list size %ld", lookups.size()); + } + + for (unsigned i = 0; i < m_num_lookups; ++i) { + if (!ParseLookupTable(data + lookups[i], length - lookups[i])) { + return Error("Failed to parse lookup %d", i); + } + } + + return true; +} + +bool ParseClassDefTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t num_classes) { + Buffer subtable(data, length); + + uint16_t format = 0; + if (!subtable.ReadU16(&format)) { + return OTS_FAILURE_MSG("Failed to read class defn format"); + } + if (format == 1) { + return ParseClassDefFormat1(font, data, length, num_glyphs, num_classes); + } else if (format == 2) { + return ParseClassDefFormat2(font, data, length, num_glyphs, num_classes); + } + + return OTS_FAILURE_MSG("Bad class defn format %d", format); +} + +bool ParseCoverageTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t expected_num_glyphs) { + Buffer subtable(data, length); + + uint16_t format = 0; + if (!subtable.ReadU16(&format)) { + return OTS_FAILURE_MSG("Failed to read coverage table format"); + } + if (format == 1) { + return ParseCoverageFormat1(font, data, length, num_glyphs, expected_num_glyphs); + } else if (format == 2) { + return ParseCoverageFormat2(font, data, length, num_glyphs, expected_num_glyphs); + } + + return OTS_FAILURE_MSG("Bad coverage table format %d", format); +} + +bool ParseDeviceTable(const ots::Font *font, + const uint8_t *data, size_t length) { + Buffer subtable(data, length); + + uint16_t start_size = 0; + uint16_t end_size = 0; + uint16_t delta_format = 0; + if (!subtable.ReadU16(&start_size) || + !subtable.ReadU16(&end_size) || + !subtable.ReadU16(&delta_format)) { + return OTS_FAILURE_MSG("Failed to read device table header"); + } + if (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 device table size range: %u > %u", start_size, end_size); + } + if (delta_format == 0 || delta_format > kMaxDeltaFormatType) { + 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. + const unsigned num_units = (end_size - start_size) / + (1 << (4 - delta_format)) + 1; + // Just skip |num_units| * 2 bytes since the compressed data could take + // arbitrary values. + if (!subtable.Skip(num_units * 2)) { + return OTS_FAILURE_MSG("Failed to skip data in device table"); + } + return true; +} + +bool OpenTypeLayoutTable::ParseContextSubtable(const uint8_t *data, + const size_t length) { + Font *font = GetFont(); + Buffer subtable(data, length); + + uint16_t format = 0; + if (!subtable.ReadU16(&format)) { + return Error("Failed to read context subtable format"); + } + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + + if (format == 1) { + if (!ParseContextFormat1(font, data, length, maxp->num_glyphs, m_num_lookups)) { + return Error("Failed to parse context format 1 subtable"); + } + } else if (format == 2) { + if (!ParseContextFormat2(font, data, length, maxp->num_glyphs, m_num_lookups)) { + return Error("Failed to parse context format 2 subtable"); + } + } else if (format == 3) { + if (!ParseContextFormat3(font, data, length, maxp->num_glyphs, m_num_lookups)) { + return Error("Failed to parse context format 3 subtable"); + } + } else { + return Error("Bad context subtable format %d", format); + } + + return true; +} + +bool OpenTypeLayoutTable::ParseChainingContextSubtable(const uint8_t *data, + const size_t length) { + Font *font = GetFont(); + Buffer subtable(data, length); + + uint16_t format = 0; + if (!subtable.ReadU16(&format)) { + return Error("Failed to read chaining context subtable format"); + } + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + font->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + + if (format == 1) { + if (!ParseChainContextFormat1(font, data, length, maxp->num_glyphs, m_num_lookups)) { + return Error("Failed to parse chaining context format 1 subtable"); + } + } else if (format == 2) { + if (!ParseChainContextFormat2(font, data, length, maxp->num_glyphs, m_num_lookups)) { + return Error("Failed to parse chaining context format 2 subtable"); + } + } else if (format == 3) { + if (!ParseChainContextFormat3(font, data, length, maxp->num_glyphs, m_num_lookups)) { + return Error("Failed to parse chaining context format 3 subtable"); + } + } else { + return Error("Bad chaining context subtable format %d", format); + } + + return true; +} + +bool OpenTypeLayoutTable::ParseExtensionSubtable(const uint8_t *data, + const size_t length) { + Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t lookup_type = 0; + uint32_t offset_extension = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&lookup_type) || + !subtable.ReadU32(&offset_extension)) { + return Error("Failed to read extension table header"); + } + + if (format != 1) { + return Error("Bad extension table format %d", format); + } + // |lookup_type| should be other than |parser->extension_type|. + if (!ValidLookupSubtableType(lookup_type, true)) { + return Error("Bad lookup type %d in extension table", lookup_type); + } + + const unsigned format_end = static_cast<unsigned>(8); + if (offset_extension < format_end || + offset_extension >= length) { + return Error("Bad extension offset %d", offset_extension); + } + + // Parse the extension subtable of |lookup_type|. + if (!ParseLookupSubtable(data + offset_extension, length - offset_extension, + lookup_type)) { + return Error("Failed to parse lookup from extension lookup"); + } + + return true; +} + +// Parsing feature variations table (in GSUB/GPOS v1.1) +bool OpenTypeLayoutTable::ParseFeatureVariationsTable(const uint8_t *data, const size_t length) { + Font *font = GetFont(); + 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 Error("Failed to read feature variations table header"); + } + + OpenTypeFVAR* fvar = static_cast<OpenTypeFVAR*>(font->GetTypedTable(OTS_TAG_FVAR)); + if (!fvar) { + return Error("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 Error("Failed to read feature variation record"); + } + + if (condition_set_offset) { + if (condition_set_offset < kEndOfFeatureVariationRecords || + condition_set_offset >= length) { + return Error("Condition set offset out of range"); + } + if (!ParseConditionSetTable(font, data + condition_set_offset, + length - condition_set_offset, + axis_count)) { + return Error("Failed to parse condition set table"); + } + } + + if (feature_table_substitution_offset) { + if (feature_table_substitution_offset < kEndOfFeatureVariationRecords || + feature_table_substitution_offset >= length) { + return Error("Feature table substitution offset out of range"); + } + if (!ParseFeatureTableSubstitutionTable(font, data + feature_table_substitution_offset, + length - feature_table_substitution_offset, + m_num_lookups)) { + return Error("Failed to parse feature table substitution table"); + } + } + } + + return true; +} + +// GSUB/GPOS header size for table version 1.0 +const size_t kHeaderSize_1_0 = 4 + 3 * 2; +// GSUB/GPOS header size for table versio 1.1 +const size_t kHeaderSize_1_1 = 4 + 3 * 2 + 4; + +bool OpenTypeLayoutTable::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + 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; + 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 Error("Incomplete table"); + } + + 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) ? kHeaderSize_1_0 : kHeaderSize_1_1; + + if (offset_lookup_list) { + if (offset_lookup_list < header_size || offset_lookup_list >= length) { + return Error("Bad lookup list offset in table header"); + } + + if (!ParseLookupListTable(data + offset_lookup_list, + length - offset_lookup_list)) { + return Error("Failed to parse lookup list table"); + } + } + + if (offset_feature_list) { + if (offset_feature_list < header_size || offset_feature_list >= length) { + return Error("Bad feature list offset in table header"); + } + + if (!ParseFeatureListTable(data + offset_feature_list, + length - offset_feature_list)) { + return Error("Failed to parse feature list table"); + } + } + + if (offset_script_list) { + if (offset_script_list < header_size || offset_script_list >= length) { + return Error("Bad script list offset in table header"); + } + + if (!ParseScriptListTable(data + offset_script_list, + length - offset_script_list)) { + return Error("Failed to parse script list table"); + } + } + + if (offset_feature_variations) { + if (offset_feature_variations < header_size || offset_feature_variations >= length) { + return Error("Bad feature variations offset in table header"); + } + + if (!ParseFeatureVariationsTable(data + offset_feature_variations, + length - offset_feature_variations)) { + return Error("Failed to parse feature variations table"); + } + } + + this->m_data = data; + this->m_length = length; + return true; +} + +bool OpenTypeLayoutTable::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write table"); + } + + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/layout.h b/gfx/ots/src/layout.h new file mode 100644 index 0000000000..f0bc133e66 --- /dev/null +++ b/gfx/ots/src/layout.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef OTS_LAYOUT_H_ +#define OTS_LAYOUT_H_ + +#include "ots.h" + +// Utility functions for OpenType layout common table formats. +// http://www.microsoft.com/typography/otspec/chapter2.htm + +namespace ots { + +// The maximum number of class value. +const uint16_t kMaxClassDefValue = 0xFFFF; + +class OpenTypeLayoutTable : public Table { + public: + explicit OpenTypeLayoutTable(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); + + protected: + bool ParseContextSubtable(const uint8_t *data, const size_t length); + bool ParseChainingContextSubtable(const uint8_t *data, const size_t length); + bool ParseExtensionSubtable(const uint8_t *data, const size_t length); + + private: + bool ParseScriptListTable(const uint8_t *data, const size_t length); + bool ParseFeatureListTable(const uint8_t *data, const size_t length); + bool ParseLookupListTable(const uint8_t *data, const size_t length); + bool ParseFeatureVariationsTable(const uint8_t *data, const size_t length); + bool ParseLookupTable(const uint8_t *data, const size_t length); + + virtual bool ValidLookupSubtableType(const uint16_t lookup_type, + bool extension = false) const = 0; + virtual bool ParseLookupSubtable(const uint8_t *data, const size_t length, + const uint16_t lookup_type) = 0; + + const uint8_t *m_data = nullptr; + size_t m_length = 0; + uint16_t m_num_features = 0; + uint16_t m_num_lookups = 0; +}; + +bool ParseClassDefTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t num_classes); + +bool ParseCoverageTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t expected_num_glyphs = 0); + +bool ParseDeviceTable(const ots::Font *font, + const uint8_t *data, size_t length); + +} // namespace ots + +#endif // OTS_LAYOUT_H_ + diff --git a/gfx/ots/src/loca.cc b/gfx/ots/src/loca.cc new file mode 100644 index 0000000000..4f322027d6 --- /dev/null +++ b/gfx/ots/src/loca.cc @@ -0,0 +1,93 @@ +// 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 "loca.h" + +#include "head.h" +#include "maxp.h" + +// loca - Index to Location +// http://www.microsoft.com/typography/otspec/loca.htm + +namespace ots { + +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. + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); + if (!maxp || !head) { + return Error("Required maxp or head tables are missing"); + } + + const unsigned num_glyphs = maxp->num_glyphs; + unsigned last_offset = 0; + this->offsets.resize(num_glyphs + 1); + // maxp->num_glyphs is uint16_t, thus the addition never overflows. + + 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 Error("Failed to read offset for glyph %d", i); + } + if (offset < last_offset) { + return Error("Out of order offset %d < %d for glyph %d", offset, last_offset, i); + } + last_offset = offset; + this->offsets[i] = offset * 2; + } + } else { + for (unsigned i = 0; i <= num_glyphs; ++i) { + uint32_t offset = 0; + if (!table.ReadU32(&offset)) { + return Error("Failed to read offset for glyph %d", i); + } + if (offset < last_offset) { + return Error("Out of order offset %d < %d for glyph %d", offset, last_offset, i); + } + last_offset = offset; + this->offsets[i] = offset; + } + } + + return true; +} + +bool OpenTypeLOCA::Serialize(OTSStream *out) { + OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>( + GetFont()->GetTypedTable(OTS_TAG_HEAD)); + + if (!head) { + return Error("Required head table is missing"); + } + + if (head->index_to_loc_format == 0) { + for (unsigned i = 0; i < this->offsets.size(); ++i) { + const uint16_t offset = static_cast<uint16_t>(this->offsets[i] >> 1); + if ((offset != (this->offsets[i] >> 1)) || + !out->WriteU16(offset)) { + return Error("Failed to write glyph offset for glyph %d", i); + } + } + } else { + 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); + } + } + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/loca.h b/gfx/ots/src/loca.h new file mode 100644 index 0000000000..da3241842f --- /dev/null +++ b/gfx/ots/src/loca.h @@ -0,0 +1,27 @@ +// 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_LOCA_H_ +#define OTS_LOCA_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +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<uint32_t> offsets; +}; + +} // namespace ots + +#endif // OTS_LOCA_H_ diff --git a/gfx/ots/src/ltsh.cc b/gfx/ots/src/ltsh.cc new file mode 100644 index 0000000000..4c40c5eb77 --- /dev/null +++ b/gfx/ots/src/ltsh.cc @@ -0,0 +1,71 @@ +// 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 "ltsh.h" + +#include "maxp.h" + +// LTSH - Linear Threshold +// http://www.microsoft.com/typography/otspec/ltsh.htm + +namespace ots { + +bool OpenTypeLTSH::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table is missing"); + } + + uint16_t num_glyphs = 0; + if (!table.ReadU16(&this->version) || + !table.ReadU16(&num_glyphs)) { + return Error("Failed to read table header"); + } + + if (this->version != 0) { + return Drop("Unsupported version: %u", this->version); + } + + if (num_glyphs != maxp->num_glyphs) { + return Drop("Bad numGlyphs: %u", num_glyphs); + } + + this->ypels.reserve(num_glyphs); + for (unsigned i = 0; i < num_glyphs; ++i) { + uint8_t pel = 0; + if (!table.ReadU8(&pel)) { + return Error("Failed to read pixels for glyph %d", i); + } + this->ypels.push_back(pel); + } + + return true; +} + +bool OpenTypeLTSH::Serialize(OTSStream *out) { + const uint16_t num_ypels = static_cast<uint16_t>(this->ypels.size()); + if (num_ypels != this->ypels.size() || + !out->WriteU16(this->version) || + !out->WriteU16(num_ypels)) { + return Error("Failed to write table header"); + } + for (uint16_t i = 0; i < num_ypels; ++i) { + if (!out->Write(&(this->ypels[i]), 1)) { + return Error("Failed to write pixel size for glyph %d", i); + } + } + + return true; +} + +bool OpenTypeLTSH::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; +} + +} // namespace ots diff --git a/gfx/ots/src/ltsh.h b/gfx/ots/src/ltsh.h new file mode 100644 index 0000000000..cc9fbf62db --- /dev/null +++ b/gfx/ots/src/ltsh.h @@ -0,0 +1,30 @@ +// 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_LTSH_H_ +#define OTS_LTSH_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +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<uint8_t> ypels; +}; + +} // namespace ots + +#endif // OTS_LTSH_H_ diff --git a/gfx/ots/src/math.cc b/gfx/ots/src/math.cc new file mode 100644 index 0000000000..c94e3bee36 --- /dev/null +++ b/gfx/ots/src/math.cc @@ -0,0 +1,584 @@ +// 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. + +// We use an underscore to avoid confusion with the standard math.h library. +#include "math_.h" + +#include <limits> +#include <vector> + +#include "layout.h" +#include "maxp.h" + +// MATH - The MATH Table +// http://www.microsoft.com/typography/otspec/math.htm + +namespace { + +// The size of MATH header. +// Version +// MathConstants +// MathGlyphInfo +// MathVariants +const unsigned kMathHeaderSize = 4 + 3 * 2; + +// The size of the MathGlyphInfo header. +// MathItalicsCorrectionInfo +// MathTopAccentAttachment +// ExtendedShapeCoverage +// MathKernInfo +const unsigned kMathGlyphInfoHeaderSize = 4 * 2; + +// The size of the MathValueRecord. +// Value +// DeviceTable +const unsigned kMathValueRecordSize = 2 * 2; + +// The size of the GlyphPartRecord. +// glyph +// StartConnectorLength +// EndConnectorLength +// FullAdvance +// PartFlags +const unsigned kGlyphPartRecordSize = 5 * 2; + +} // namespace + +namespace ots { + +// Shared Table: MathValueRecord + +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(); + } + + // Check the offset to device table. + uint16_t offset = 0; + if (!subtable->ReadU16(&offset)) { + return OTS_FAILURE(); + } + if (offset) { + if (offset >= length) { + return OTS_FAILURE(); + } + if (!ots::ParseDeviceTable(GetFont(), data + offset, length - offset)) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool OpenTypeMATH::ParseMathConstantsTable(const uint8_t *data, + size_t length) { + ots::Buffer subtable(data, length); + + // Part 1: int16 or uint16 constants. + // ScriptPercentScaleDown + // ScriptScriptPercentScaleDown + // DelimitedSubFormulaMinHeight + // DisplayOperatorMinHeight + if (!subtable.Skip(4 * 2)) { + return OTS_FAILURE(); + } + + // Part 2: MathValueRecord constants. + // MathLeading + // AxisHeight + // AccentBaseHeight + // FlattenedAccentBaseHeight + // SubscriptShiftDown + // SubscriptTopMax + // SubscriptBaselineDropMin + // SuperscriptShiftUp + // SuperscriptShiftUpCramped + // SuperscriptBottomMin + // + // SuperscriptBaselineDropMax + // SubSuperscriptGapMin + // SuperscriptBottomMaxWithSubscript + // SpaceAfterScript + // UpperLimitGapMin + // UpperLimitBaselineRiseMin + // LowerLimitGapMin + // LowerLimitBaselineDropMin + // StackTopShiftUp + // StackTopDisplayStyleShiftUp + // + // StackBottomShiftDown + // StackBottomDisplayStyleShiftDown + // StackGapMin + // StackDisplayStyleGapMin + // StretchStackTopShiftUp + // StretchStackBottomShiftDown + // StretchStackGapAboveMin + // StretchStackGapBelowMin + // FractionNumeratorShiftUp + // FractionNumeratorDisplayStyleShiftUp + // + // FractionDenominatorShiftDown + // FractionDenominatorDisplayStyleShiftDown + // FractionNumeratorGapMin + // FractionNumDisplayStyleGapMin + // FractionRuleThickness + // FractionDenominatorGapMin + // FractionDenomDisplayStyleGapMin + // SkewedFractionHorizontalGap + // SkewedFractionVerticalGap + // OverbarVerticalGap + // + // OverbarRuleThickness + // OverbarExtraAscender + // UnderbarVerticalGap + // UnderbarRuleThickness + // UnderbarExtraDescender + // RadicalVerticalGap + // RadicalDisplayStyleVerticalGap + // RadicalRuleThickness + // RadicalExtraAscender + // RadicalKernBeforeDegree + // + // RadicalKernAfterDegree + for (unsigned i = 0; i < static_cast<unsigned>(51); ++i) { + if (!ParseMathValueRecord(&subtable, data, length)) { + return OTS_FAILURE(); + } + } + + // Part 3: uint16 constant + // RadicalDegreeBottomRaisePercent + if (!subtable.Skip(2)) { + return OTS_FAILURE(); + } + + return true; +} + +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; + if (!subtable->ReadU16(&offset_coverage) || + !subtable->ReadU16(&sequence_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = static_cast<unsigned>(2 * 2) + + sequence_count * kMathValueRecordSize; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + // Check coverage table. + if (offset_coverage < sequence_end || offset_coverage >= length) { + return OTS_FAILURE(); + } + if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, + length - offset_coverage, + num_glyphs, sequence_count)) { + return OTS_FAILURE(); + } + + // Check sequence. + for (unsigned i = 0; i < sequence_count; ++i) { + if (!ParseMathValueRecord(subtable, data, length)) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool OpenTypeMATH::ParseMathItalicsCorrectionInfoTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + return ParseMathValueRecordSequenceForGlyphs(&subtable, data, length, + num_glyphs); +} + +bool OpenTypeMATH::ParseMathTopAccentAttachmentTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + return ParseMathValueRecordSequenceForGlyphs(&subtable, data, length, + num_glyphs); +} + +bool OpenTypeMATH::ParseMathKernTable(const uint8_t *data, size_t length) { + ots::Buffer subtable(data, length); + + // Check the Height count. + uint16_t height_count = 0; + if (!subtable.ReadU16(&height_count)) { + return OTS_FAILURE(); + } + + // Check the Correction Heights. + for (unsigned i = 0; i < height_count; ++i) { + if (!ParseMathValueRecord(&subtable, data, length)) { + return OTS_FAILURE(); + } + } + + // Check the Kern Values. + for (unsigned i = 0; i <= height_count; ++i) { + if (!ParseMathValueRecord(&subtable, data, length)) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool OpenTypeMATH::ParseMathKernInfoTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Check the header. + uint16_t offset_coverage = 0; + uint16_t sequence_count = 0; + if (!subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&sequence_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = static_cast<unsigned>(2 * 2) + + sequence_count * 4 * 2; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + // Check coverage table. + if (offset_coverage < sequence_end || offset_coverage >= length) { + return OTS_FAILURE(); + } + if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, length - offset_coverage, + num_glyphs, sequence_count)) { + return OTS_FAILURE(); + } + + // Check sequence of MathKernInfoRecord + for (unsigned i = 0; i < sequence_count; ++i) { + // Check TopRight, TopLeft, BottomRight and BottomLeft Math Kern. + for (unsigned j = 0; j < 4; ++j) { + uint16_t offset_math_kern = 0; + if (!subtable.ReadU16(&offset_math_kern)) { + return OTS_FAILURE(); + } + if (offset_math_kern) { + if (offset_math_kern < sequence_end || offset_math_kern >= length || + !ParseMathKernTable(data + offset_math_kern, + length - offset_math_kern)) { + return OTS_FAILURE(); + } + } + } + } + + return true; +} + +bool OpenTypeMATH::ParseMathGlyphInfoTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Check Header. + uint16_t offset_math_italics_correction_info = 0; + uint16_t offset_math_top_accent_attachment = 0; + uint16_t offset_extended_shaped_coverage = 0; + uint16_t offset_math_kern_info = 0; + if (!subtable.ReadU16(&offset_math_italics_correction_info) || + !subtable.ReadU16(&offset_math_top_accent_attachment) || + !subtable.ReadU16(&offset_extended_shaped_coverage) || + !subtable.ReadU16(&offset_math_kern_info)) { + return OTS_FAILURE(); + } + + // Check subtables. + // The specification does not say whether the offsets for + // MathItalicsCorrectionInfo, MathTopAccentAttachment and MathKernInfo may + // be NULL, but that's the case in some fonts (e.g STIX) so we accept that. + if (offset_math_italics_correction_info) { + if (offset_math_italics_correction_info >= length || + offset_math_italics_correction_info < kMathGlyphInfoHeaderSize || + !ParseMathItalicsCorrectionInfoTable( + data + offset_math_italics_correction_info, + length - offset_math_italics_correction_info, + num_glyphs)) { + return OTS_FAILURE(); + } + } + if (offset_math_top_accent_attachment) { + if (offset_math_top_accent_attachment >= length || + offset_math_top_accent_attachment < kMathGlyphInfoHeaderSize || + !ParseMathTopAccentAttachmentTable(data + + offset_math_top_accent_attachment, + length - + offset_math_top_accent_attachment, + num_glyphs)) { + return OTS_FAILURE(); + } + } + if (offset_extended_shaped_coverage) { + if (offset_extended_shaped_coverage >= length || + offset_extended_shaped_coverage < kMathGlyphInfoHeaderSize || + !ots::ParseCoverageTable(GetFont(), data + offset_extended_shaped_coverage, + length - offset_extended_shaped_coverage, + num_glyphs)) { + return OTS_FAILURE(); + } + } + if (offset_math_kern_info) { + if (offset_math_kern_info >= length || + offset_math_kern_info < kMathGlyphInfoHeaderSize || + !ParseMathKernInfoTable(data + offset_math_kern_info, + length - offset_math_kern_info, num_glyphs)) { + return OTS_FAILURE(); + } + } + + return true; +} + +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(&subtable, data, length) || + !subtable.ReadU16(&part_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = kMathValueRecordSize + + static_cast<unsigned>(2) + part_count * kGlyphPartRecordSize; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + // Check the sequence of GlyphPartRecord. + for (unsigned i = 0; i < part_count; ++i) { + uint16_t glyph = 0; + uint16_t part_flags = 0; + if (!subtable.ReadU16(&glyph) || + !subtable.Skip(2 * 3) || + !subtable.ReadU16(&part_flags)) { + return OTS_FAILURE(); + } + if (glyph >= num_glyphs) { + return Error("bad glyph ID: %u", glyph); + } + if (part_flags & ~0x00000001) { + return Error("unknown part flag: %u", part_flags); + } + } + + return true; +} + +bool OpenTypeMATH::ParseMathGlyphConstructionTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Check the header. + uint16_t offset_glyph_assembly = 0; + uint16_t variant_count = 0; + if (!subtable.ReadU16(&offset_glyph_assembly) || + !subtable.ReadU16(&variant_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = static_cast<unsigned>(2 * 2) + + variant_count * 2 * 2; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + // Check the GlyphAssembly offset. + if (offset_glyph_assembly) { + if (offset_glyph_assembly >= length || + offset_glyph_assembly < sequence_end) { + return OTS_FAILURE(); + } + if (!ParseGlyphAssemblyTable(data + offset_glyph_assembly, + length - offset_glyph_assembly, num_glyphs)) { + return OTS_FAILURE(); + } + } + + // Check the sequence of MathGlyphVariantRecord. + for (unsigned i = 0; i < variant_count; ++i) { + uint16_t glyph = 0; + if (!subtable.ReadU16(&glyph) || + !subtable.Skip(2)) { + return OTS_FAILURE(); + } + if (glyph >= num_glyphs) { + return Error("bad glyph ID: %u", glyph); + } + } + + return true; +} + +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; + } + + // Check coverage table. + if (offset_coverage < sequence_end || offset_coverage >= length) { + return OTS_FAILURE(); + } + if (!ots::ParseCoverageTable(GetFont(), data + offset_coverage, + length - offset_coverage, + num_glyphs, glyph_count)) { + return OTS_FAILURE(); + } + + // Check sequence of MathGlyphConstruction. + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t offset_glyph_construction = 0; + if (!subtable->ReadU16(&offset_glyph_construction)) { + return OTS_FAILURE(); + } + if (offset_glyph_construction < sequence_end || + offset_glyph_construction >= length || + !ParseMathGlyphConstructionTable(data + offset_glyph_construction, + length - offset_glyph_construction, + num_glyphs)) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool OpenTypeMATH::ParseMathVariantsTable(const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Check the header. + uint16_t offset_vert_glyph_coverage = 0; + uint16_t offset_horiz_glyph_coverage = 0; + uint16_t vert_glyph_count = 0; + uint16_t horiz_glyph_count = 0; + if (!subtable.Skip(2) || // MinConnectorOverlap + !subtable.ReadU16(&offset_vert_glyph_coverage) || + !subtable.ReadU16(&offset_horiz_glyph_coverage) || + !subtable.ReadU16(&vert_glyph_count) || + !subtable.ReadU16(&horiz_glyph_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = 5 * 2 + vert_glyph_count * 2 + + horiz_glyph_count * 2; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + if (!ParseMathGlyphConstructionSequence(&subtable, data, length, num_glyphs, + offset_vert_glyph_coverage, + vert_glyph_count, + sequence_end) || + !ParseMathGlyphConstructionSequence(&subtable, data, length, num_glyphs, + offset_horiz_glyph_coverage, + horiz_glyph_count, + sequence_end)) { + return OTS_FAILURE(); + } + + return true; +} + +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. + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Required maxp table missing"); + } + const uint16_t num_glyphs = maxp->num_glyphs; + + Buffer table(data, length); + + uint32_t version = 0; + if (!table.ReadU32(&version)) { + return OTS_FAILURE(); + } + if (version != 0x00010000) { + return Drop("bad MATH version"); + } + + uint16_t offset_math_constants = 0; + uint16_t offset_math_glyph_info = 0; + uint16_t offset_math_variants = 0; + if (!table.ReadU16(&offset_math_constants) || + !table.ReadU16(&offset_math_glyph_info) || + !table.ReadU16(&offset_math_variants)) { + return OTS_FAILURE(); + } + + if (offset_math_constants >= length || + offset_math_constants < kMathHeaderSize || + offset_math_glyph_info >= length || + offset_math_glyph_info < kMathHeaderSize || + offset_math_variants >= length || + offset_math_variants < kMathHeaderSize) { + return Drop("bad offset in MATH header"); + } + + if (!ParseMathConstantsTable(data + offset_math_constants, + length - offset_math_constants)) { + return Drop("failed to parse MathConstants table"); + } + if (!ParseMathGlyphInfoTable(data + offset_math_glyph_info, + length - offset_math_glyph_info, num_glyphs)) { + return Drop("failed to parse MathGlyphInfo table"); + } + if (!ParseMathVariantsTable(data + offset_math_variants, + length - offset_math_variants, num_glyphs)) { + return Drop("failed to parse MathVariants table"); + } + + this->m_data = data; + this->m_length = length; + return true; +} + +bool OpenTypeMATH::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return OTS_FAILURE(); + } + + return true; +} + +bool OpenTypeMATH::ShouldSerialize() { + return Table::ShouldSerialize() && this->m_data != NULL; +} + +} // namespace ots diff --git a/gfx/ots/src/math_.h b/gfx/ots/src/math_.h new file mode 100644 index 0000000000..875cacd4da --- /dev/null +++ b/gfx/ots/src/math_.h @@ -0,0 +1,69 @@ +// 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. + +#ifndef OTS_MATH_H_ +#define OTS_MATH_H_ + +#include "ots.h" + +namespace ots { + +class OpenTypeMATH : public Table { + public: + explicit OpenTypeMATH(Font *font, uint32_t tag) + : Table(font, tag, tag), + m_data(NULL), + m_length(0) { } + + 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 + +#endif + diff --git a/gfx/ots/src/maxp.cc b/gfx/ots/src/maxp.cc new file mode 100644 index 0000000000..232e4a9889 --- /dev/null +++ b/gfx/ots/src/maxp.cc @@ -0,0 +1,103 @@ +// 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 "maxp.h" + +// maxp - Maximum Profile +// http://www.microsoft.com/typography/otspec/maxp.htm + +namespace ots { + +bool OpenTypeMAXP::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + uint32_t version = 0; + if (!table.ReadU32(&version)) { + return Error("Failed to read table version"); + } + + if (version >> 16 > 1) { + return Error("Unsupported table version 0x%x", version); + } + + if (!table.ReadU16(&this->num_glyphs)) { + return Error("Failed to read numGlyphs"); + } + + if (!this->num_glyphs) { + return Error("numGlyphs is 0"); + } + + if (version >> 16 == 1) { + 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 (this->max_zones == 0) { + // workaround for ipa*.ttf Japanese fonts. + Warning("Bad maxZones: %u", this->max_zones); + this->max_zones = 1; + } else if (this->max_zones == 3) { + // workaround for Ecolier-*.ttf fonts. + Warning("Bad maxZones: %u", this->max_zones); + this->max_zones = 2; + } + + if ((this->max_zones != 1) && (this->max_zones != 2)) { + return Error("Bad maxZones: %u", this->max_zones); + } + } else { + this->version_1 = false; + } + + return true; +} + +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 (!this->version_1) return true; + + 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(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(this->max_c_components) || + !out->WriteU16(this->max_c_depth)) { + return Error("Failed to write yet more maxp"); + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/maxp.h b/gfx/ots/src/maxp.h new file mode 100644 index 0000000000..99dbdc439a --- /dev/null +++ b/gfx/ots/src/maxp.h @@ -0,0 +1,42 @@ +// 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_MAXP_H_ +#define OTS_MAXP_H_ + +#include "ots.h" + +namespace ots { + +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; + + uint16_t max_points; + uint16_t max_contours; + uint16_t max_c_points; + uint16_t max_c_contours; + + uint16_t max_zones; + uint16_t max_t_points; + uint16_t max_storage; + uint16_t max_fdefs; + uint16_t max_idefs; + uint16_t max_stack; + uint16_t max_size_glyf_instructions; + + uint16_t max_c_components; + uint16_t max_c_depth; +}; + +} // namespace ots + +#endif // OTS_MAXP_H_ diff --git a/gfx/ots/src/metrics.cc b/gfx/ots/src/metrics.cc new file mode 100644 index 0000000000..3959d5d8da --- /dev/null +++ b/gfx/ots/src/metrics.cc @@ -0,0 +1,177 @@ +// 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 "metrics.h" + +#include "head.h" +#include "maxp.h" + +// OpenType horizontal and vertical common header format +// http://www.microsoft.com/typography/otspec/hhea.htm +// http://www.microsoft.com/typography/otspec/vhea.htm + +namespace ots { + +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 (this->ascent < 0) { + Warning("Negative ascent, setting to 0: %d", this->ascent); + this->ascent = 0; + } + if (this->linegap < 0) { + Warning("Negative linegap, setting to: %d", this->linegap); + this->linegap = 0; + } + + OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>( + 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 (!(head->mac_style & 2) && + (this->caret_offset != 0)) { + Warning("Non-zero caretOffset but head.macStyle italic bit is not set, setting to caretOffset to 0: %d", this->caret_offset); + this->caret_offset = 0; + } + + // skip the reserved bytes + if (!table.Skip(8)) { + return Error("Failed to read reserved bytes"); + } + + int16_t data_format; + if (!table.ReadS16(&data_format)) { + return Error("Failed to read metricDataFormat"); + } + if (data_format) { + return Error("Unsupported metricDataFormat: %d", data_format); + } + + if (!table.ReadU16(&this->num_metrics)) { + return Error("Failed to read number of metrics"); + } + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + GetFont()->GetTypedTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Missing maxp font table"); + } + + if (this->num_metrics > maxp->num_glyphs) { + return Error("Bad number of metrics %d", this->num_metrics); + } + + return true; +} + +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(this->num_metrics)) { + return Error("Failed to write metrics"); + } + + return true; +} + +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<OpenTypeMetricsHeader*>( + 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; + + OpenTypeMAXP *maxp = static_cast<OpenTypeMAXP*>( + 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 Error("No metrics!"); + } + const unsigned num_sbs = maxp->num_glyphs - 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 Error("Failed to read metric %d", i); + } + this->entries.push_back(std::make_pair(adv, sb)); + } + + this->sbs.reserve(num_sbs); + for (unsigned i = 0; i < num_sbs; ++i) { + int16_t sb; + if (!table.ReadS16(&sb)) { + // Some Japanese fonts (e.g., mona.ttf) fail this test. + return Error("Failed to read side bearing %d", i + num_metrics); + } + this->sbs.push_back(sb); + } + + return true; +} + +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 < this->sbs.size(); ++i) { + if (!out->WriteS16(this->sbs[i])) { + return Error("Failed to write side bearing %ld", i + this->entries.size()); + } + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/metrics.h b/gfx/ots/src/metrics.h new file mode 100644 index 0000000000..efe14c0709 --- /dev/null +++ b/gfx/ots/src/metrics.h @@ -0,0 +1,56 @@ +// 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. + +#ifndef OTS_METRICS_H_ +#define OTS_METRICS_H_ + +#include <new> +#include <utility> +#include <vector> + +#include "ots.h" + +namespace ots { + +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; + int16_t linegap; + uint16_t adv_width_max; + int16_t min_sb1; + int16_t min_sb2; + int16_t max_extent; + int16_t caret_slope_rise; + int16_t caret_slope_run; + int16_t caret_offset; + uint16_t num_metrics; +}; + +struct OpenTypeMetricsTable : 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<std::pair<uint16_t, int16_t> > entries; + std::vector<int16_t> sbs; +}; + +} // namespace ots + +#endif // OTS_METRICS_H_ diff --git a/gfx/ots/src/moz.build b/gfx/ots/src/moz.build new file mode 100644 index 0000000000..03b046a6bc --- /dev/null +++ b/gfx/ots/src/moz.build @@ -0,0 +1,84 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + '../include/opentype-sanitiser.h', + '../include/ots-memory-stream.h', + '../RLBoxWOFF2Types.h', +] + +UNIFIED_SOURCES += [ + '../RLBoxWOFF2Host.cpp' +] + +UNIFIED_SOURCES += [ + 'avar.cc', + 'cff.cc', + 'cff_charstring.cc', + 'cmap.cc', + 'colr.cc', + 'cpal.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', + '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', + 'vorg.cc', + 'vvar.cc', +] + +# We allow warnings for third-party code that can be updated from upstream. +AllowCompilerWarnings() + +FINAL_LIBRARY = 'gkmedias' + +DEFINES['PACKAGE_VERSION'] = '"moz"' +DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"' +DEFINES['OTS_GRAPHITE'] = 1 +DEFINES['OTS_VARIATIONS'] = 1 +DEFINES['OTS_SYNTHESIZE_MISSING_GVAR'] = 1 + +USE_LIBS += [ + 'brotli', + 'woff2', +] + +LOCAL_INCLUDES += [ + '!/security/rlbox', + '/modules/woff2/src', +] diff --git a/gfx/ots/src/mvar.cc b/gfx/ots/src/mvar.cc new file mode 100644 index 0000000000..e485ec0488 --- /dev/null +++ b/gfx/ots/src/mvar.cc @@ -0,0 +1,107 @@ +// 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( + "Out-of-order value tag: '%c%c%c%c', previous tag: '%c%c%c%c'", + OTS_UNTAG(tag), OTS_UNTAG(prevTag)); + } + 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 0000000000..81fb6155dd --- /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 new file mode 100644 index 0000000000..7526e1f72b --- /dev/null +++ b/gfx/ots/src/name.cc @@ -0,0 +1,409 @@ +// 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 "name.h" + +#include <algorithm> +#include <cstring> +#include <cctype> + +// name - Naming Table +// http://www.microsoft.com/typography/otspec/name.htm + +namespace { + +// We disallow characters outside the URI spec "unreserved characters" +// set; any chars outside this set will be replaced by underscore. +bool AllowedInPsName(char c) { + return isalnum(c) || std::strchr("-._~", c); +} + +bool SanitizePsNameAscii(std::string& name) { + if (name.size() > 63) + return false; + + for (unsigned i = 0; i < name.size(); ++i) { + if (!AllowedInPsName(name[i])) { + name[i] = '_'; + } + } + return true; +} + +bool SanitizePsNameUtf16Be(std::string& name) { + if ((name.size() & 1) != 0) + return false; + if (name.size() > 2 * 63) + return false; + + for (unsigned i = 0; i < name.size(); i += 2) { + if (name[i] != 0) { + // non-Latin1 char in psname? reject it altogether + return false; + } + if (!AllowedInPsName(name[i+1])) { + name[i] = '_'; + } + } + return true; +} + +void AssignToUtf16BeFromAscii(std::string* target, + const std::string& source) { + target->resize(source.size() * 2); + for (unsigned i = 0, j = 0; i < source.size(); i++) { + (*target)[j++] = '\0'; + (*target)[j++] = source[i]; + } +} + +} // namespace + + +namespace ots { + +bool OpenTypeNAME::Parse(const uint8_t* data, size_t length) { + Buffer table(data, length); + + uint16_t format = 0; + if (!table.ReadU16(&format) || format > 1) { + return Error("Failed to read table format or bad format %d", format); + } + + uint16_t count = 0; + if (!table.ReadU16(&count)) { + return Error("Failed to read name count"); + } + + uint16_t string_offset = 0; + if (!table.ReadU16(&string_offset) || string_offset > length) { + return Error("Failed to read or bad stringOffset"); + } + const char* string_base = reinterpret_cast<const char*>(data) + + string_offset; + + bool sort_required = false; + + // Read all the names, discarding any with invalid IDs, + // and any where the offset/length would be outside the table. + // A stricter alternative would be to reject the font if there + // are invalid name records, but it's not clear that is necessary. + for (unsigned i = 0; i < count; ++i) { + NameRecord rec; + uint16_t name_length, name_offset = 0; + if (!table.ReadU16(&rec.platform_id) || + !table.ReadU16(&rec.encoding_id) || + !table.ReadU16(&rec.language_id) || + !table.ReadU16(&rec.name_id) || + !table.ReadU16(&name_length) || + !table.ReadU16(&name_offset)) { + return Error("Failed to read name entry %d", i); + } + // check platform & encoding, discard names with unknown values + switch (rec.platform_id) { + case 0: // Unicode + if (rec.encoding_id > 6) { + continue; + } + break; + case 1: // Macintosh + if (rec.encoding_id > 32) { + continue; + } + break; + case 2: // ISO + if (rec.encoding_id > 2) { + continue; + } + break; + case 3: // Windows: IDs 7 to 9 are "reserved" + if (rec.encoding_id > 6 && rec.encoding_id != 10) { + continue; + } + break; + case 4: // Custom (OTF Windows NT compatibility) + if (rec.encoding_id > 255) { + continue; + } + break; + default: // unknown platform + continue; + } + + const unsigned name_end = static_cast<unsigned>(string_offset) + + name_offset + name_length; + if (name_end > length) { + continue; + } + rec.text.resize(name_length); + rec.text.assign(string_base + name_offset, name_length); + + if (rec.name_id == 6) { + // PostScript name: "sanitize" it by replacing any chars outside the + // URI spec "unreserved" set by underscore, or reject the name entirely + // (and use a fallback) if it looks really broken. + if (rec.platform_id == 1) { + if (!SanitizePsNameAscii(rec.text)) { + continue; + } + } else if (rec.platform_id == 0 || rec.platform_id == 3) { + if (!SanitizePsNameUtf16Be(rec.text)) { + continue; + } + } + } + + if (!this->names.empty() && !(this->names.back() < rec)) { + Warning("name records are not sorted."); + sort_required = true; + } + + 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 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 Error("Faile to read length or offset for langTagRecord %d", i); + } + const unsigned tag_end = static_cast<unsigned>(string_offset) + + tag_offset + tag_length; + if (tag_end > length) { + 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); + 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 Error("Bad table offset %ld > %d", table.offset(), string_offset); + } + + // check existence of required name strings (synthesize if necessary) + // [0 - copyright - skip] + // 1 - family + // 2 - subfamily + // [3 - unique ID - skip] + // 4 - full name + // 5 - version + // 6 - postscript name + static const uint16_t kStdNameCount = 7; + static const char* kStdNames[kStdNameCount] = { + NULL, + "OTS derived font", + "Unspecified", + NULL, + "OTS derived font", + "1.000", + "OTS-derived-font" + }; + + // scan the names to check whether the required "standard" ones are present; + // if not, we'll add our fixed versions here + bool mac_name[kStdNameCount] = { 0 }; + bool win_name[kStdNameCount] = { 0 }; + for (const auto& name : this->names) { + const uint16_t id = name.name_id; + if (id >= kStdNameCount || kStdNames[id] == NULL) { + continue; + } + if (name.platform_id == 1) { + mac_name[id] = true; + continue; + } + if (name.platform_id == 3) { + win_name[id] = true; + continue; + } + } + + for (uint16_t i = 0; i < kStdNameCount; ++i) { + if (kStdNames[i] == NULL) { + continue; + } + if (!mac_name[i] && !win_name[i]) { + NameRecord mac_rec(1 /* platform_id */, 0 /* encoding_id */, + 0 /* language_id */ , i /* name_id */); + mac_rec.text.assign(kStdNames[i]); + + NameRecord win_rec(3 /* platform_id */, 1 /* encoding_id */, + 1033 /* language_id */ , i /* name_id */); + AssignToUtf16BeFromAscii(&win_rec.text, std::string(kStdNames[i])); + + this->names.push_back(mac_rec); + this->names.push_back(win_rec); + sort_required = true; + } + } + + if (sort_required) { + std::sort(this->names.begin(), this->names.end()); + } + + return true; +} + +bool OpenTypeNAME::Serialize(OTSStream* out) { + uint16_t name_count = static_cast<uint16_t>(this->names.size()); + uint16_t lang_tag_count = static_cast<uint16_t>(this->lang_tags.size()); + uint16_t format = 0; + size_t string_offset = 6 + name_count * 12; + + 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 Error("Bad stringOffset: %ld", string_offset); + } + if (!out->WriteU16(format) || + !out->WriteU16(name_count) || + !out->WriteU16(static_cast<uint16_t>(string_offset))) { + return Error("Failed to write name header"); + } + + std::string string_data; + for (const auto& rec : this->names) { + if (string_data.size() + rec.text.size() > + std::numeric_limits<uint16_t>::max() || + !out->WriteU16(rec.platform_id) || + !out->WriteU16(rec.encoding_id) || + !out->WriteU16(rec.language_id) || + !out->WriteU16(rec.name_id) || + !out->WriteU16(static_cast<uint16_t>(rec.text.size())) || + !out->WriteU16(static_cast<uint16_t>(string_data.size())) ) { + return Error("Faile to write nameRecord"); + } + string_data.append(rec.text); + } + + if (format == 1) { + if (!out->WriteU16(lang_tag_count)) { + return Error("Faile to write langTagCount"); + } + for (const auto& tag : this->lang_tags) { + if (string_data.size() + tag.size() > + std::numeric_limits<uint16_t>::max() || + !out->WriteU16(static_cast<uint16_t>(tag.size())) || + !out->WriteU16(static_cast<uint16_t>(string_data.size()))) { + return Error("Failed to write langTagRecord"); + } + string_data.append(tag); + } + } + + if (!out->Write(string_data.data(), string_data.size())) { + return Error("Faile to write string data"); + } + + return true; +} + +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); +} + +// List of font names considered "tricky" (dependent on applying original TrueType instructions) by FreeType, see +// https://gitlab.freedesktop.org/freetype/freetype/-/blob/2d9fce53d4ce89f36075168282fcdd7289e082f9/src/truetype/ttobjs.c#L170-241 +static const char* tricky_font_names[] = { + "cpop", + "DFGirl-W6-WIN-BF", + "DFGothic-EB", + "DFGyoSho-Lt", + "DFHei", + "DFHSGothic-W5", + "DFHSMincho-W3", + "DFHSMincho-W7", + "DFKaiSho-SB", + "DFKaiShu", + "DFKai-SB", + "DFMing", + "DLC", + "HuaTianKaiTi?", + "HuaTianSongTi?", + "Ming(for ISO10646)", + "MingLiU", + "MingMedium", + "PMingLiU", + "MingLi43" +}; + +bool OpenTypeNAME::IsTrickyFont() const { + for (const auto& name : this->names) { + const uint16_t id = name.name_id; + if (id != 1) { + continue; + } + for (const auto* p : tricky_font_names) { + if (name.text.find(p) != std::string::npos) { + return true; + } + } + } + return false; +} + +} // namespace diff --git a/gfx/ots/src/name.h b/gfx/ots/src/name.h new file mode 100644 index 0000000000..a241e77ee2 --- /dev/null +++ b/gfx/ots/src/name.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef OTS_NAME_H_ +#define OTS_NAME_H_ + +#include <new> +#include <string> +#include <utility> +#include <vector> +#include <unordered_set> + +#include "ots.h" + +namespace ots { + +struct NameRecord { + NameRecord() { + } + + NameRecord(uint16_t platformID, uint16_t encodingID, + uint16_t languageID, uint16_t nameID) + : platform_id(platformID), + encoding_id(encodingID), + language_id(languageID), + name_id(nameID) { + } + + uint16_t platform_id; + uint16_t encoding_id; + uint16_t language_id; + uint16_t name_id; + std::string text; + + bool operator<(const NameRecord& rhs) const { + if (platform_id < rhs.platform_id) return true; + if (platform_id > rhs.platform_id) return false; + if (encoding_id < rhs.encoding_id) return true; + if (encoding_id > rhs.encoding_id) return false; + if (language_id < rhs.language_id) return true; + if (language_id > rhs.language_id) return false; + return name_id < rhs.name_id; + } +}; + +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); + bool IsTrickyFont() const; + + private: + std::vector<NameRecord> names; + std::vector<std::string> lang_tags; + std::unordered_set<uint16_t> name_ids; +}; + +} // namespace ots + +#endif // OTS_NAME_H_ diff --git a/gfx/ots/src/os2.cc b/gfx/ots/src/os2.cc new file mode 100644 index 0000000000..5376a1dbb1 --- /dev/null +++ b/gfx/ots/src/os2.cc @@ -0,0 +1,320 @@ +// 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 <string> + +#include "os2.h" +#include "head.h" + +// OS/2 - OS/2 and Windows Metrics +// http://www.microsoft.com/typography/otspec/os2.htm + +namespace ots { + +bool OpenTypeOS2::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + 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 (this->table.type & 0x2) { + // mask bits 2 & 3. + this->table.type &= 0xfff3u; + } else if (this->table.type & 0x4) { + // mask bits 1 & 3. + this->table.type &= 0xfff4u; + } else if (this->table.type & 0x8) { + // mask bits 1 & 2. + this->table.type &= 0xfff9u; + } + + // mask reserved bits. use only 0..3, 8, 9 bits. + this->table.type &= 0x30f; + +#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); + SET_TO_ZERO("ySubscriptYSize", subscript_y_size); + SET_TO_ZERO("ySuperscriptXSize", superscript_x_size); + SET_TO_ZERO("ySuperscriptYSize", superscript_y_size); + SET_TO_ZERO("yStrikeoutSize", strikeout_size); +#undef SET_TO_ZERO + + static const char* panose_strings[10] = { + "bFamilyType", + "bSerifStyle", + "bWeight", + "bProportion", + "bContrast", + "bStrokeVariation", + "bArmStyle", + "bLetterform", + "bMidline", + "bXHeight", + }; + for (unsigned i = 0; i < 10; ++i) { + if (!table.ReadU8(&this->table.panose[i])) { + return Error("Failed to read PANOSE %s", panose_strings[i]); + } + } + + 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 (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. + OpenTypeHEAD *head = static_cast<OpenTypeHEAD*>( + 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 ((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 ((this->table.selection & 0x40) && + head && (head->mac_style & 0x3)) { + Warning("Adjusting head.macStyle (regular) to match fsSelection"); + head->mac_style &= 0xfffcu; + } + + if ((this->table.version < 4) && + (this->table.selection & 0x300)) { + // bit 8 and 9 must be unset in OS/2 table versions less than 4. + Warning("fsSelection bits 8 and 9 must be unset for table version %d", + this->table.version); + } + + // mask reserved bits. use only 0..9 bits. + this->table.selection &= 0x3ff; + + 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 (this->table.typo_linegap < 0) { + Warning("Bad sTypoLineGap, setting it to 0: %d", this->table.typo_linegap); + this->table.typo_linegap = 0; + } + + if (this->table.version < 1) { + // http://www.microsoft.com/typography/otspec/os2ver0.htm + return true; + } + + 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. + this->table.version = 0; + return true; + } + + 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 (this->table.version < 2) { + // http://www.microsoft.com/typography/otspec/os2ver1.htm + return true; + } + + 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. + this->table.version = 1; + return true; + } + + 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 (this->table.x_height < 0) { + Warning("Bad sxHeight settig it to 0: %d", this->table.x_height); + this->table.x_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 (this->table.version < 5) { + // http://www.microsoft.com/typography/otspec/os2ver4.htm + return true; + } + + 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 (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 (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 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(&this->table.panose[i], 1)) { + return Error("Failed to write PANOSE data"); + } + } + + 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 (this->table.version < 1) { + return true; + } + + 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 (this->table.version < 2) { + return true; + } + + 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 (this->table.version < 5) { + return true; + } + + 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; +} + +} // namespace ots diff --git a/gfx/ots/src/os2.h b/gfx/ots/src/os2.h new file mode 100644 index 0000000000..b3f1bad9b3 --- /dev/null +++ b/gfx/ots/src/os2.h @@ -0,0 +1,67 @@ +// 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_OS2_H_ +#define OTS_OS2_H_ + +#include "ots.h" + +namespace ots { + +struct OS2Data { + uint16_t version; + int16_t avg_char_width; + uint16_t weight_class; + uint16_t width_class; + uint16_t type; + int16_t subscript_x_size; + int16_t subscript_y_size; + int16_t subscript_x_offset; + int16_t subscript_y_offset; + int16_t superscript_x_size; + int16_t superscript_y_size; + int16_t superscript_x_offset; + int16_t superscript_y_offset; + int16_t strikeout_size; + int16_t strikeout_position; + int16_t family_class; + uint8_t panose[10]; + uint32_t unicode_range_1; + uint32_t unicode_range_2; + uint32_t unicode_range_3; + uint32_t unicode_range_4; + uint32_t vendor_id; + uint16_t selection; + uint16_t first_char_index; + uint16_t last_char_index; + int16_t typo_ascender; + int16_t typo_descender; + int16_t typo_linegap; + uint16_t win_ascent; + uint16_t win_descent; + uint32_t code_page_range_1; + uint32_t code_page_range_2; + int16_t x_height; + int16_t cap_height; + uint16_t default_char; + uint16_t break_char; + uint16_t max_context; + uint16_t lower_optical_pointsize; + uint16_t upper_optical_pointsize; +}; + +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 new file mode 100644 index 0000000000..73b1d235cf --- /dev/null +++ b/gfx/ots/src/ots.cc @@ -0,0 +1,1151 @@ +// 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 "ots.h" + +#include <sys/types.h> +#include <zlib.h> + +#include <algorithm> +#include <cstdlib> +#include <cstring> +#include <limits> +#include <map> +#include <vector> + +#include "../RLBoxWOFF2Host.h" + +// The OpenType Font File +// http://www.microsoft.com/typography/otspec/otff.htm + +#include "avar.h" +#include "cff.h" +#include "cmap.h" +#include "colr.h" +#include "cpal.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 ots { + +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; + } + + private: + std::vector<uint8_t*> hunks_; +}; + +bool CheckTag(uint32_t tag_value) { + for (unsigned i = 0; i < 4; ++i) { + const uint32_t check = tag_value & 0xff; + if (check < 32 || check > 126) { + return false; // non-ASCII character found. + } + tag_value >>= 8; + } + return true; +} + +}; // namespace ots + +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__) + + +const struct { + uint32_t tag; + bool required; +} 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 }, + // Color font tables. + // We need to parse CPAL before COLR so that the number of palette entries + // is known; and these tables follow fvar because COLR may use variations. + { OTS_TAG_CPAL, false }, + { OTS_TAG_COLR, false }, + // We need to parse GDEF table in advance of parsing GSUB/GPOS tables + // because they could refer GDEF table. + { 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 ValidateVersionTag(ots::Font *font) { + switch (font->version) { + case 0x000010000: + case OTS_TAG('O','T','T','O'): + return true; + case OTS_TAG('t','r','u','e'): + font->version = 0x000010000; + return true; + default: + return false; + } +} + +bool ProcessGeneric(ots::FontFile *header, + ots::Font *font, + uint32_t signature, + ots::OTSStream *output, + const uint8_t *data, size_t length, + const std::vector<ots::TableEntry>& tables, + ots::Buffer& file); + +bool ProcessTTF(ots::FontFile *header, + ots::Font *font, + ots::OTSStream *output, const uint8_t *data, size_t length, + uint32_t offset = 0) { + ots::Buffer file(data + offset, length - offset); + + if (offset > length) { + return OTS_FAILURE_MSG_HDR("offset beyond end of file"); + } + + // we disallow all files > 1GB in size for sanity. + if (length > 1024 * 1024 * 1024) { + return OTS_FAILURE_MSG_HDR("file exceeds 1GB"); + } + + if (!file.ReadU32(&font->version)) { + return OTS_FAILURE_MSG_HDR("error reading sfntVersion"); + } + if (!ValidateVersionTag(font)) { + return OTS_FAILURE_MSG_HDR("invalid sfntVersion: %d", font->version); + } + + if (!file.ReadU16(&font->num_tables) || + !file.ReadU16(&font->search_range) || + !file.ReadU16(&font->entry_selector) || + !file.ReadU16(&font->range_shift)) { + return OTS_FAILURE_MSG_HDR("error reading table directory search header"); + } + + // search_range is (Maximum power of 2 <= numTables) x 16. Thus, to avoid + // overflow num_tables is, at most, 2^16 / 16 = 2^12 + if (font->num_tables >= 4096 || font->num_tables < 1) { + return OTS_FAILURE_MSG_HDR("excessive (or zero) number of tables"); + } + + unsigned max_pow2 = 0; + while (1u << (max_pow2 + 1) <= font->num_tables) { + max_pow2++; + } + const uint16_t expected_search_range = (1u << max_pow2) << 4; + + // Don't call ots_failure() here since ~25% of fonts (250+ fonts) in + // http://www.princexml.com/fonts/ have unexpected search_range value. + if (font->search_range != expected_search_range) { + OTS_WARNING_MSG_HDR("bad table directory searchRange"); + font->search_range = expected_search_range; // Fix the value. + } + + // entry_selector is Log2(maximum power of 2 <= numTables) + if (font->entry_selector != max_pow2) { + OTS_WARNING_MSG_HDR("bad table directory entrySelector"); + font->entry_selector = max_pow2; // Fix the value. + } + + // range_shift is NumTables x 16-searchRange. We know that 16*num_tables + // doesn't over flow because we range checked it above. Also, we know that + // it's > font->search_range by construction of search_range. + const uint16_t expected_range_shift = + 16 * font->num_tables - font->search_range; + if (font->range_shift != expected_range_shift) { + OTS_WARNING_MSG_HDR("bad table directory rangeShift"); + font->range_shift = expected_range_shift; // the same as above. + } + + // Next up is the list of tables. + std::vector<ots::TableEntry> tables; + + for (unsigned i = 0; i < font->num_tables; ++i) { + ots::TableEntry table; + if (!file.ReadU32(&table.tag) || + !file.ReadU32(&table.chksum) || + !file.ReadU32(&table.offset) || + !file.ReadU32(&table.length)) { + return OTS_FAILURE_MSG_HDR("error reading table directory"); + } + + table.uncompressed_length = table.length; + tables.push_back(table); + } + + return ProcessGeneric(header, font, font->version, output, data, length, + tables, file); +} + +bool ProcessTTC(ots::FontFile *header, + ots::OTSStream *output, + const uint8_t *data, + size_t length, + uint32_t index) { + ots::Buffer file(data, length); + + // we disallow all files > 1GB in size for sanity. + if (length > 1024 * 1024 * 1024) { + return OTS_FAILURE_MSG_HDR("file exceeds 1GB"); + } + + uint32_t ttc_tag; + if (!file.ReadU32(&ttc_tag)) { + return OTS_FAILURE_MSG_HDR("Error reading TTC tag"); + } + if (ttc_tag != OTS_TAG('t','t','c','f')) { + return OTS_FAILURE_MSG_HDR("Invalid TTC tag"); + } + + uint32_t ttc_version; + if (!file.ReadU32(&ttc_version)) { + return OTS_FAILURE_MSG_HDR("Error reading TTC version"); + } + if (ttc_version != 0x00010000 && ttc_version != 0x00020000) { + return OTS_FAILURE_MSG_HDR("Invalid TTC version"); + } + + uint32_t num_fonts; + if (!file.ReadU32(&num_fonts)) { + return OTS_FAILURE_MSG_HDR("Error reading number of TTC fonts"); + } + // Limit the allowed number of subfonts to have same memory allocation. + if (num_fonts > 0x10000) { + return OTS_FAILURE_MSG_HDR("Too many fonts in TTC"); + } + + std::vector<uint32_t> offsets(num_fonts); + for (unsigned i = 0; i < num_fonts; i++) { + if (!file.ReadU32(&offsets[i])) { + return OTS_FAILURE_MSG_HDR("Error reading offset to OffsetTable"); + } + } + + if (ttc_version == 0x00020000) { + // We don't care about these fields of the header: + // uint32_t dsig_tag, dsig_length, dsig_offset + if (!file.Skip(3 * 4)) { + return OTS_FAILURE_MSG_HDR("Error reading DSIG offset and length in TTC font"); + } + } + + if (index == static_cast<uint32_t>(-1)) { + if (!output->WriteU32(ttc_tag) || + !output->WriteU32(0x00010000) || + !output->WriteU32(num_fonts) || + !output->Seek((3 + num_fonts) * 4)) { + return OTS_FAILURE_MSG_HDR("Error writing output"); + } + + // Keep references to the fonts processed in the loop below, as we need + // them for reused tables. + std::vector<ots::Font> fonts(num_fonts, ots::Font(header)); + + for (unsigned i = 0; i < num_fonts; i++) { + uint32_t out_offset = output->Tell(); + if (!output->Seek((3 + i) * 4) || + !output->WriteU32(out_offset) || + !output->Seek(out_offset)) { + return OTS_FAILURE_MSG_HDR("Error writing output"); + } + if (!ProcessTTF(header, &fonts[i], output, data, length, offsets[i])) { + return false; + } + } + + return true; + } else { + if (index >= num_fonts) { + return OTS_FAILURE_MSG_HDR("Requested font index is bigger than the number of fonts in the TTC file"); + } + + ots::Font font(header); + return ProcessTTF(header, &font, output, data, length, offsets[index]); + } +} + +bool ProcessWOFF(ots::FontFile *header, + ots::Font *font, + ots::OTSStream *output, const uint8_t *data, size_t length) { + ots::Buffer file(data, length); + + // we disallow all files > 1GB in size for sanity. + if (length > 1024 * 1024 * 1024) { + return OTS_FAILURE_MSG_HDR("file exceeds 1GB"); + } + + uint32_t woff_tag; + if (!file.ReadU32(&woff_tag)) { + return OTS_FAILURE_MSG_HDR("error reading WOFF marker"); + } + + if (woff_tag != OTS_TAG('w','O','F','F')) { + return OTS_FAILURE_MSG_HDR("invalid WOFF marker"); + } + + if (!file.ReadU32(&font->version)) { + return OTS_FAILURE_MSG_HDR("error reading sfntVersion"); + } + if (!ValidateVersionTag(font)) { + return OTS_FAILURE_MSG_HDR("invalid sfntVersion: %d", font->version); + } + + uint32_t reported_length; + if (!file.ReadU32(&reported_length) || length != reported_length) { + return OTS_FAILURE_MSG_HDR("incorrect file size in WOFF header"); + } + + if (!file.ReadU16(&font->num_tables) || !font->num_tables) { + return OTS_FAILURE_MSG_HDR("error reading number of tables"); + } + + uint16_t reserved_value; + if (!file.ReadU16(&reserved_value) || reserved_value) { + return OTS_FAILURE_MSG_HDR("error in reserved field of WOFF header"); + } + + uint32_t reported_total_sfnt_size; + if (!file.ReadU32(&reported_total_sfnt_size)) { + return OTS_FAILURE_MSG_HDR("error reading total sfnt size"); + } + + // We don't care about these fields of the header: + // uint16_t major_version, minor_version + if (!file.Skip(2 * 2)) { + return OTS_FAILURE_MSG_HDR("Failed to read 'majorVersion' or 'minorVersion'"); + } + + // Checks metadata block size. + uint32_t meta_offset; + uint32_t meta_length; + uint32_t meta_length_orig; + if (!file.ReadU32(&meta_offset) || + !file.ReadU32(&meta_length) || + !file.ReadU32(&meta_length_orig)) { + return OTS_FAILURE_MSG_HDR("Failed to read header metadata block fields"); + } + if (meta_offset) { + if (meta_offset >= length || length - meta_offset < meta_length) { + return OTS_FAILURE_MSG_HDR("Invalid metadata block offset or length"); + } + } + + // Checks private data block size. + uint32_t priv_offset; + uint32_t priv_length; + if (!file.ReadU32(&priv_offset) || + !file.ReadU32(&priv_length)) { + return OTS_FAILURE_MSG_HDR("Failed to read header private block fields"); + } + if (priv_offset) { + if (priv_offset >= length || length - priv_offset < priv_length) { + return OTS_FAILURE_MSG_HDR("Invalid private block offset or length"); + } + } + + // Next up is the list of tables. + std::vector<ots::TableEntry> 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) { + ots::TableEntry table; + if (!file.ReadU32(&table.tag) || + !file.ReadU32(&table.offset) || + !file.ReadU32(&table.length) || + !file.ReadU32(&table.uncompressed_length) || + !file.ReadU32(&table.chksum)) { + return OTS_FAILURE_MSG_HDR("error reading table directory"); + } + + total_sfnt_size += ots::Round4(table.uncompressed_length); + if (total_sfnt_size > std::numeric_limits<uint32_t>::max()) { + return OTS_FAILURE_MSG_HDR("sfnt size overflow"); + } + tables.push_back(table); + if (i == 0 || tables[first_index].offset > table.offset) + first_index = i; + if (i == 0 || tables[last_index].offset < table.offset) + last_index = i; + } + + if (reported_total_sfnt_size != total_sfnt_size) { + return OTS_FAILURE_MSG_HDR("uncompressed sfnt size mismatch"); + } + + // Table data must follow immediately after the header. + if (tables[first_index].offset != ots::Round4(file.offset())) { + return OTS_FAILURE_MSG_HDR("junk before tables in WOFF file"); + } + + if (tables[last_index].offset >= length || + length - tables[last_index].offset < tables[last_index].length) { + return OTS_FAILURE_MSG_HDR("invalid table location/size"); + } + // Blocks must follow immediately after the previous block. + // (Except for padding with a maximum of three null bytes) + uint64_t block_end = ots::Round4( + static_cast<uint64_t>(tables[last_index].offset) + + static_cast<uint64_t>(tables[last_index].length)); + if (block_end > std::numeric_limits<uint32_t>::max()) { + return OTS_FAILURE_MSG_HDR("invalid table location/size"); + } + if (meta_offset) { + if (block_end != meta_offset) { + return OTS_FAILURE_MSG_HDR("Invalid metadata block offset"); + } + block_end = ots::Round4(static_cast<uint64_t>(meta_offset) + + static_cast<uint64_t>(meta_length)); + if (block_end > std::numeric_limits<uint32_t>::max()) { + return OTS_FAILURE_MSG_HDR("Invalid metadata block length"); + } + } + if (priv_offset) { + if (block_end != priv_offset) { + return OTS_FAILURE_MSG_HDR("Invalid private block offset"); + } + block_end = ots::Round4(static_cast<uint64_t>(priv_offset) + + static_cast<uint64_t>(priv_length)); + if (block_end > std::numeric_limits<uint32_t>::max()) { + return OTS_FAILURE_MSG_HDR("Invalid private block length"); + } + } + if (block_end != ots::Round4(length)) { + return OTS_FAILURE_MSG_HDR("File length mismatch (trailing junk?)"); + } + + return ProcessGeneric(header, font, woff_tag, output, data, length, tables, file); +} + +bool ProcessWOFF2(ots::FontFile* header, ots::OTSStream* output, + const uint8_t* data, size_t length, uint32_t index) { + return RLBoxProcessWOFF2(header, output, data, length, index, ProcessTTC, ProcessTTF); +} + +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 (supported_tables[i].tag == 0) break; + + if (supported_tables[i].tag == tag) { + action = ots::TABLE_ACTION_SANITIZE; + break; + } + } + } + + assert(action != ots::TABLE_ACTION_DEFAULT); // Should never return this. + return action; +} + +bool GetTableData(const uint8_t *data, + const 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); + uLongf dest_len = *table_length; + int r = uncompress((Bytef*) *table_data, &dest_len, + data + table.offset, table.length); + if (r != Z_OK || dest_len != *table_length) { + return false; + } + } else { + // Uncompressed table. We can process directly from memory. + *table_data = data + table.offset; + *table_length = table.length; + } + + return true; +} + +bool ProcessGeneric(ots::FontFile *header, + ots::Font *font, + uint32_t signature, + ots::OTSStream *output, + const uint8_t *data, size_t length, + const std::vector<ots::TableEntry>& tables, + ots::Buffer& file) { + const size_t data_offset = file.offset(); + + uint32_t uncompressed_sum = 0; + + for (unsigned i = 0; i < font->num_tables; ++i) { + // the tables must be sorted by tag (when taken as big-endian numbers). + // This also remove the possibility of duplicate tables. + if (i) { + const uint32_t this_tag = tables[i].tag; + const uint32_t prev_tag = tables[i - 1].tag; + if (this_tag <= prev_tag) { + OTS_WARNING_MSG_HDR("Table directory is not correctly ordered"); + } + } + + // all tag names must be built from printable ASCII characters + if (!ots::CheckTag(tables[i].tag)) { + OTS_WARNING_MSG_HDR("Invalid table tag: 0x%X", tables[i].tag); + } + + // tables must be 4-byte aligned + if (tables[i].offset & 3) { + return OTS_FAILURE_MSG_TAG("misaligned table", tables[i].tag); + } + + // and must be within the file + if (tables[i].offset < data_offset || tables[i].offset >= length) { + return OTS_FAILURE_MSG_TAG("invalid table offset", tables[i].tag); + } + // disallow all tables with a zero length + if (tables[i].length < 1) { + // Note: malayalam.ttf has zero length CVT table... + return OTS_FAILURE_MSG_TAG("zero-length table", tables[i].tag); + } + // disallow all tables with a length > 1GB + if (tables[i].length > 1024 * 1024 * 1024) { + return OTS_FAILURE_MSG_TAG("table length exceeds 1GB", tables[i].tag); + } + // disallow tables where the uncompressed size is < the compressed size. + if (tables[i].uncompressed_length < tables[i].length) { + return OTS_FAILURE_MSG_TAG("invalid compressed table", tables[i].tag); + } + if (tables[i].uncompressed_length > tables[i].length) { + // We'll probably be decompressing this table. + + // disallow all tables which decompress to > OTS_MAX_DECOMPRESSED_TABLE_SIZE + if (tables[i].uncompressed_length > OTS_MAX_DECOMPRESSED_TABLE_SIZE) { + return OTS_FAILURE_MSG_HDR("%c%c%c%c: decompressed table length exceeds %gMB", + OTS_UNTAG(tables[i].tag), + OTS_MAX_DECOMPRESSED_TABLE_SIZE / (1024.0 * 1024.0)); + } + if (uncompressed_sum + tables[i].uncompressed_length < uncompressed_sum) { + return OTS_FAILURE_MSG_TAG("overflow of decompressed sum", tables[i].tag); + } + + uncompressed_sum += tables[i].uncompressed_length; + } + // since we required that the file be < 1GB in length, and that the table + // length is < 1GB, the following addtion doesn't overflow + uint32_t end_byte = tables[i].offset + tables[i].length; + // Tables in the WOFF file must be aligned 4-byte boundary. + if (signature == OTS_TAG('w','O','F','F')) { + end_byte = ots::Round4(end_byte); + } + if (!end_byte || end_byte > length) { + return OTS_FAILURE_MSG_TAG("table overruns end of file", tables[i].tag); + } + } + + // All decompressed tables decompressed must be <= OTS_MAX_DECOMPRESSED_FILE_SIZE. + if (uncompressed_sum > OTS_MAX_DECOMPRESSED_FILE_SIZE) { + return OTS_FAILURE_MSG_HDR("decompressed sum exceeds %gMB", + OTS_MAX_DECOMPRESSED_FILE_SIZE / (1024.0 * 1024.0)); + } + + if (uncompressed_sum > output->size()) { + return OTS_FAILURE_MSG_HDR("decompressed sum exceeds output size (%gMB)", output->size() / (1024.0 * 1024.0)); + } + + // check that the tables are not overlapping. + std::vector<std::pair<uint32_t, uint8_t> > overlap_checker; + for (unsigned i = 0; i < font->num_tables; ++i) { + overlap_checker.push_back( + std::make_pair(tables[i].offset, static_cast<uint8_t>(1) /* start */)); + overlap_checker.push_back( + std::make_pair(tables[i].offset + tables[i].length, + static_cast<uint8_t>(0) /* end */)); + } + std::sort(overlap_checker.begin(), overlap_checker.end()); + int overlap_count = 0; + for (unsigned i = 0; i < overlap_checker.size(); ++i) { + overlap_count += (overlap_checker[i].second ? 1 : -1); + if (overlap_count > 1) { + return OTS_FAILURE_MSG_HDR("overlapping tables"); + } + } + + std::map<uint32_t, ots::TableEntry> 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 (supported_tables[i].tag == 0) break; + + 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); + } + } else { + if (!font->ParseTable(it->second, data, arena)) { + return OTS_FAILURE_MSG_TAG("Failed to parse table", tag); + } + } + } + + // 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); + } + } + } + +#ifdef OTS_SYNTHESIZE_MISSING_GVAR + // If there was an fvar table but no gvar, synthesize an empty gvar to avoid + // issues with rasterizers (e.g. Core Text) that assume it must be present. + if (font->GetTable(OTS_TAG_FVAR) && !font->GetTable(OTS_TAG_GVAR)) { + ots::TableEntry table_entry{ OTS_TAG_GVAR, 0, 0, 0, 0 }; + const auto &it = font->file->tables.find(table_entry); + if (it != font->file->tables.end()) { + table_map[OTS_TAG_GVAR] = table_entry; + font->AddTable(table_entry, it->second); + } else { + ots::OpenTypeGVAR *gvar = new ots::OpenTypeGVAR(font, OTS_TAG_GVAR); + if (gvar->InitEmpty()) { + table_map[OTS_TAG_GVAR] = table_entry; + font->AddTable(table_entry, gvar); + } else { + delete gvar; + } + } + } +#endif + + ots::Table *glyf = font->GetTable(OTS_TAG_GLYF); + ots::Table *loca = font->GetTable(OTS_TAG_LOCA); + ots::Table *cff = font->GetTable(OTS_TAG_CFF); + ots::Table *cff2 = font->GetTable(OTS_TAG_CFF2); + + if (glyf && loca) { + if (font->version != 0x000010000) { + OTS_WARNING_MSG_HDR("wrong sfntVersion for glyph data"); + font->version = 0x000010000; + } + if (cff) + cff->Drop("font contains both CFF and glyf/loca tables"); + if (cff2) + cff2->Drop("font contains both CFF and glyf/loca tables"); + } else if (cff || cff2) { + if (font->version != OTS_TAG('O','T','T','O')) { + OTS_WARNING_MSG_HDR("wrong sfntVersion for glyph data"); + font->version = OTS_TAG('O','T','T','O'); + } + if (glyf) + glyf->Drop("font contains both CFF and glyf tables"); + if (loca) + loca->Drop("font contains both CFF and loca tables"); + } else if (font->GetTable(OTS_TAG('C','B','D','T')) && + font->GetTable(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. + } else { + return OTS_FAILURE_MSG_HDR("no supported glyph data table(s) present"); + } + + uint16_t num_output_tables = 0; + for (const auto &it : table_map) { + ots::Table *table = font->GetTable(it.first); + if (table) + num_output_tables++; + } + + uint16_t max_pow2 = 0; + while (1u << (max_pow2 + 1) <= num_output_tables) { + max_pow2++; + } + const uint16_t output_search_range = (1u << max_pow2) << 4; + + // most of the errors here are highly unlikely - they'd only occur if the + // output stream returns a failure, e.g. lack of space to write + output->ResetChecksum(); + if (!output->WriteU32(font->version) || + !output->WriteU16(num_output_tables) || + !output->WriteU16(output_search_range) || + !output->WriteU16(max_pow2) || + !output->WriteU16((num_output_tables << 4) - output_search_range)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + const uint32_t offset_table_chksum = output->chksum(); + + const size_t table_record_offset = output->Tell(); + if (!output->Pad(16 * num_output_tables)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + + std::vector<ots::TableEntry> out_tables; + + size_t head_table_offset = 0; + 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::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::Table *table = font->GetTable(out.tag); + if (table) { + output->ResetChecksum(); + 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_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_TAG("Failed to pad table to 4 bytes", out.tag); + } + out.chksum = output->chksum(); + out_tables.push_back(out); + header->table_entries[input_offset] = out; + } + } + } + + const size_t end_of_file = output->Tell(); + + // Need to sort the output tables for inclusion in the file + std::sort(out_tables.begin(), out_tables.end()); + if (!output->Seek(table_record_offset)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + + output->ResetChecksum(); + uint32_t tables_chksum = 0; + for (unsigned i = 0; i < out_tables.size(); ++i) { + if (!output->WriteU32(out_tables[i].tag) || + !output->WriteU32(out_tables[i].chksum) || + !output->WriteU32(out_tables[i].offset) || + !output->WriteU32(out_tables[i].length)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + tables_chksum += out_tables[i].chksum; + } + const uint32_t table_record_chksum = output->chksum(); + + // http://www.microsoft.com/typography/otspec/otff.htm + const uint32_t file_chksum + = offset_table_chksum + tables_chksum + table_record_chksum; + const uint32_t chksum_magic = static_cast<uint32_t>(0xb1b0afba) - file_chksum; + + // seek into the 'head' table and write in the checksum magic value + if (!head_table_offset) { + return OTS_FAILURE_MSG_HDR("internal error!"); + } + if (!output->Seek(head_table_offset + 8)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + if (!output->WriteU32(chksum_magic)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + + if (!output->Seek(end_of_file)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + + return true; +} + +bool IsGraphiteTag(uint32_t tag) { + if (tag == OTS_TAG_FEAT || + tag == OTS_TAG_GLAT || + tag == OTS_TAG_GLOC || + tag == OTS_TAG_SILE || + tag == OTS_TAG_SILF || + tag == OTS_TAG_SILL) { + return true; + } + return false; +} + +bool IsVariationsTag(uint32_t tag) { + if (tag == OTS_TAG_AVAR || + tag == OTS_TAG_CVAR || + tag == OTS_TAG_FVAR || + tag == OTS_TAG_GVAR || + tag == OTS_TAG_HVAR || + tag == OTS_TAG_MVAR || + tag == OTS_TAG_STAT || + tag == OTS_TAG_VVAR) { + return true; + } + return false; +} + +} // namespace + +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_COLR: table = new OpenTypeCOLR(this, tag); break; + case OTS_TAG_CPAL: table = new OpenTypeCPAL(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) { + ret = table->Parse(table_data, table_length); + if (ret) + AddTable(table_entry, table); + } + } + + 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() && it->second && it->second->ShouldSerialize()) + 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::AddTable(TableEntry entry, Table* table) { + // Attempting to add a duplicate table would be an error; this should only + // be used to add a table that does not already exist. + assert(m_tables.find(table->Tag()) == m_tables.end()); + m_tables[table->Tag()] = table; + file->tables[entry] = table; +} + +void Font::DropGraphite() { + file->context->Message(0, "Dropping all Graphite tables"); + for (const std::pair<uint32_t, Table*> entry : m_tables) { + if (IsGraphiteTag(entry.first)) { + entry.second->Drop("Discarding Graphite table"); + } + } +} + +void Font::DropVariations() { + file->context->Message(0, "Dropping all Variation tables"); + for (const std::pair<uint32_t, Table*> entry : m_tables) { + if (IsVariationsTag(entry.first)) { + entry.second->Drop("Discarding Variations table"); + } + } +} + +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(); + if (IsGraphiteTag(m_tag)) + Drop("Discarding Graphite table"); + + 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(); + if (IsVariationsTag(m_tag)) + Drop("Discarding Variations table"); + + 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 OTSContext::Process(OTSStream *output, + const uint8_t *data, + size_t length, + uint32_t index) { + FontFile header; + Font font(&header); + header.context = this; + + if (length < 4) { + return OTS_FAILURE_MSG_(&header, "file less than 4 bytes"); + } + + bool result; + if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == 'F') { + result = ProcessWOFF(&header, &font, output, data, length); + } else if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == '2') { + result = ProcessWOFF2(&header, output, data, length, index); + } else if (data[0] == 't' && data[1] == 't' && data[2] == 'c' && data[3] == 'f') { + result = ProcessTTC(&header, output, data, length, index); + } else { + result = ProcessTTF(&header, &font, output, data, length); + } + + return result; +} + +} // namespace ots diff --git a/gfx/ots/src/ots.h b/gfx/ots/src/ots.h new file mode 100644 index 0000000000..434e068d48 --- /dev/null +++ b/gfx/ots/src/ots.h @@ -0,0 +1,364 @@ +// 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 <stddef.h> +#include <cstdarg> +#include <cstddef> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <limits> +#include <map> + +#include "opentype-sanitiser.h" + +// arraysize borrowed from base/basictypes.h +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +namespace ots { + +#if !defined(OTS_DEBUG) +#define OTS_FAILURE() false +#else +#define OTS_FAILURE() \ + (\ + std::fprintf(stderr, "ERROR at %s:%d (%s)\n", \ + __FILE__, __LINE__, __FUNCTION__) \ + && false\ + ) +#endif + +// All OTS_FAILURE_* macros ultimately evaluate to 'false', just like the original +// message-less OTS_FAILURE(), so that the current parser will return 'false' as +// its result (indicating a failure). + +#if !defined(OTS_DEBUG) +#define OTS_MESSAGE_(level,otf_,...) \ + (otf_)->context->Message(level,__VA_ARGS__) +#else +#define OTS_MESSAGE_(level,otf_,...) \ + OTS_FAILURE(), \ + (otf_)->context->Message(level,__VA_ARGS__) +#endif + +// Generate a simple message +#define OTS_FAILURE_MSG_(otf_,...) \ + (OTS_MESSAGE_(0,otf_,__VA_ARGS__), false) + +#define OTS_WARNING_MSG_(otf_,...) \ + OTS_MESSAGE_(1,otf_,__VA_ARGS__) + +// 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 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__) + +// ----------------------------------------------------------------------------- +// Buffer helper class +// +// This class perform some trival buffer operations while checking for +// out-of-bounds errors. As a family they return false if anything is amiss, +// updating the current offset otherwise. +// ----------------------------------------------------------------------------- +class Buffer { + public: + Buffer(const uint8_t *buf, size_t len) + : buffer_(buf), + length_(len), + offset_(0) { } + + bool Skip(size_t n_bytes) { + return Read(NULL, n_bytes); + } + + bool Read(uint8_t *buf, size_t n_bytes) { + if (n_bytes > 1024 * 1024 * 1024) { + return OTS_FAILURE(); + } + if ((offset_ + n_bytes > length_) || + (offset_ > length_ - n_bytes)) { + return OTS_FAILURE(); + } + if (buf) { + std::memcpy(buf, buffer_ + offset_, n_bytes); + } + offset_ += n_bytes; + return true; + } + + inline bool ReadU8(uint8_t *value) { + if (offset_ + 1 > length_) { + return OTS_FAILURE(); + } + *value = buffer_[offset_]; + ++offset_; + return true; + } + + bool ReadU16(uint16_t *value) { + if (offset_ + 2 > length_) { + return OTS_FAILURE(); + } + std::memcpy(value, buffer_ + offset_, sizeof(uint16_t)); + *value = ots_ntohs(*value); + offset_ += 2; + return true; + } + + bool ReadS16(int16_t *value) { + return ReadU16(reinterpret_cast<uint16_t*>(value)); + } + + bool ReadU24(uint32_t *value) { + if (offset_ + 3 > length_) { + return OTS_FAILURE(); + } + *value = static_cast<uint32_t>(buffer_[offset_]) << 16 | + static_cast<uint32_t>(buffer_[offset_ + 1]) << 8 | + static_cast<uint32_t>(buffer_[offset_ + 2]); + offset_ += 3; + return true; + } + + bool ReadU32(uint32_t *value) { + if (offset_ + 4 > length_) { + return OTS_FAILURE(); + } + std::memcpy(value, buffer_ + offset_, sizeof(uint32_t)); + *value = ots_ntohl(*value); + offset_ += 4; + return true; + } + + bool ReadS32(int32_t *value) { + return ReadU32(reinterpret_cast<uint32_t*>(value)); + } + + bool ReadR64(uint64_t *value) { + if (offset_ + 8 > length_) { + return OTS_FAILURE(); + } + std::memcpy(value, buffer_ + offset_, sizeof(uint64_t)); + offset_ += 8; + return true; + } + + const uint8_t *buffer() const { return buffer_; } + size_t offset() const { return offset_; } + size_t length() const { return length_; } + size_t remaining() const { return length_ - offset_; } + + void set_offset(size_t newoffset) { offset_ = newoffset; } + + private: + const uint8_t * const buffer_; + const size_t length_; + size_t offset_; +}; + +// Round a value up to the nearest multiple of 4. Don't round the value in the +// case that rounding up overflows. +template<typename T> T Round4(T value) { + if (std::numeric_limits<T>::max() - value < 3) { + return value; + } + return (value + 3) & ~3; +} + +template<typename T> T Round2(T value) { + if (value == std::numeric_limits<T>::max()) { + return value; + } + return (value + 1) & ~1; +} + +// Check that a tag consists entirely of printable ASCII characters +bool CheckTag(uint32_t tag_value); + +#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_COLR OTS_TAG('C','O','L','R') +#define OTS_TAG_CPAL OTS_TAG('C','P','A','L') +#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') + +// See https://github.com/khaledhosny/ots/issues/219 +#define OTS_MAX_DECOMPRESSED_FILE_SIZE 300 * 1024 * 1024 +#define OTS_MAX_DECOMPRESSED_TABLE_SIZE 150 * 1024 * 1024 + +struct Font; +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() { } + + 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; } + + // Return the tag assigned when this table was constructed. + uint32_t Tag() { return m_tag; } + + 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(FontFile *f) + : file(f), + version(0), + num_tables(0), + search_range(0), + entry_selector(0), + range_shift(0) { + } + + 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; + + // Insert a new table. Asserts if a table with the same tag already exists. + void AddTable(TableEntry entry, Table* table); + + // Drop all Graphite tables and don't parse new ones. + void DropGraphite(); + + // 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; + + private: + std::map<uint32_t, Table*> m_tables; +}; + +struct TableEntry { + uint32_t tag; + uint32_t offset; + uint32_t length; + uint32_t uncompressed_length; + uint32_t chksum; + + bool operator<(const TableEntry& other) const { + return tag < other.tag; + } +}; + +struct FontFile { + ~FontFile(); + + OTSContext *context; + std::map<TableEntry, Table*> tables; + std::map<uint32_t, TableEntry> table_entries; +}; + +} // namespace ots + +#endif // OTS_H_ diff --git a/gfx/ots/src/post.cc b/gfx/ots/src/post.cc new file mode 100644 index 0000000000..2fb897250d --- /dev/null +++ b/gfx/ots/src/post.cc @@ -0,0 +1,177 @@ +// 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 "post.h" + +#include "maxp.h" + +// post - PostScript +// http://www.microsoft.com/typography/otspec/post.htm + +namespace ots { + +bool OpenTypePOST::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (!table.ReadU32(&this->version)) { + return Error("Failed to read table version"); + } + + 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 (!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 (this->underline_thickness < 0) { + this->underline_thickness = 1; + } + + if (this->version == 0x00010000 || this->version == 0x00030000) { + return true; + } + + // We have a version 2 table with a list of Pascal strings at the end + + uint16_t num_glyphs = 0; + if (!table.ReadU16(&num_glyphs)) { + return Error("Failed to read numberOfGlyphs"); + } + + OpenTypeMAXP* maxp = static_cast<OpenTypeMAXP*> + (GetFont()->GetTable(OTS_TAG_MAXP)); + if (!maxp) { + return Error("Missing required maxp table"); + } + + if (num_glyphs == 0) { + 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"); + } + // workaround for fonts in http://www.fontsquirrel.com/fontface + // (e.g., yataghan.ttf). + this->version = 0x00010000; + return Warning("Table version is 1, but no glyph names are found"); + } + + if (num_glyphs != maxp->num_glyphs) { + // Note: Fixedsys500c.ttf seems to have inconsistent num_glyphs values. + return Error("Bad number of glyphs: %d", num_glyphs); + } + + this->glyph_name_index.resize(num_glyphs); + for (unsigned i = 0; i < num_glyphs; ++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 + // which cover all of unicode. + } + + // Now we have an array of Pascal strings. We have to check that they are all + // valid and read them in. + const size_t strings_offset = table.offset(); + const uint8_t *strings = data + strings_offset; + const uint8_t *strings_end = data + length; + + for (;;) { + if (strings == strings_end) break; + const unsigned string_length = *strings; + if (strings + 1 + string_length > strings_end) { + return Error("Bad string length %d", string_length); + } + if (std::memchr(strings + 1, '\0', string_length)) { + return Error("Bad string of length %d", string_length); + } + this->names.push_back( + std::string(reinterpret_cast<const char*>(strings + 1), string_length)); + strings += 1 + string_length; + } + 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 = this->glyph_name_index[i]; + if (offset < 258) { + continue; + } + + offset -= 258; + if (offset >= num_strings) { + return Error("Bad string index %d", offset); + } + } + + return true; +} + +bool OpenTypePOST::Serialize(OTSStream *out) { + // OpenType with CFF glyphs must have v3 post table. + if (GetFont()->GetTable(OTS_TAG_CFF) && this->version != 0x00030000) { + Warning("Only version supported for fonts with CFF table is 0x00030000" + " not 0x%x", this->version); + this->version = 0x00030000; + } + + 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 Error("Failed to write post header"); + } + + if (this->version != 0x00020000) { + return true; // v1.0 and v3.0 does not have glyph names. + } + + const uint16_t num_indexes = + static_cast<uint16_t>(this->glyph_name_index.size()); + if (num_indexes != this->glyph_name_index.size() || + !out->WriteU16(num_indexes)) { + return Error("Failed to write number of indices"); + } + + for (uint16_t i = 0; i < num_indexes; ++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 < this->names.size(); ++i) { + const std::string& s = this->names[i]; + const uint8_t string_length = static_cast<uint8_t>(s.size()); + if (string_length != s.size() || + !out->Write(&string_length, 1)) { + 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 Error("Failed to write string length for string %d", i); + } + } + + return true; +} + +} // namespace ots diff --git a/gfx/ots/src/post.h b/gfx/ots/src/post.h new file mode 100644 index 0000000000..c341e391c2 --- /dev/null +++ b/gfx/ots/src/post.h @@ -0,0 +1,37 @@ +// 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_POST_H_ +#define OTS_POST_H_ + +#include "ots.h" + +#include <map> +#include <string> +#include <vector> + +namespace ots { + +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; + int16_t underline_thickness; + uint32_t is_fixed_pitch; + + std::vector<uint16_t> glyph_name_index; + std::vector<std::string> names; +}; + +} // namespace ots + +#endif // OTS_POST_H_ diff --git a/gfx/ots/src/prep.cc b/gfx/ots/src/prep.cc new file mode 100644 index 0000000000..943bb45b91 --- /dev/null +++ b/gfx/ots/src/prep.cc @@ -0,0 +1,43 @@ +// 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 "prep.h" + +// prep - Control Value Program +// http://www.microsoft.com/typography/otspec/prep.htm + +namespace ots { + +bool OpenTypePREP::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (length >= 128 * 1024u) { + // almost all prep tables are less than 9k bytes. + return Error("Table length %ld > 120K", length); + } + + if (!table.Skip(length)) { + return Error("Failed to read table of length %ld", length); + } + + this->m_data = data; + this->m_length = length; + return true; +} + +bool OpenTypePREP::Serialize(OTSStream *out) { + if (!out->Write(this->m_data, this->m_length)) { + return Error("Failed to write table length"); + } + + return true; +} + +bool OpenTypePREP::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; +} + +} // namespace ots diff --git a/gfx/ots/src/prep.h b/gfx/ots/src/prep.h new file mode 100644 index 0000000000..4d3eda2cd2 --- /dev/null +++ b/gfx/ots/src/prep.h @@ -0,0 +1,28 @@ +// 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_PREP_H_ +#define OTS_PREP_H_ + +#include "ots.h" + +namespace ots { + +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 + +#endif // OTS_PREP_H_ diff --git a/gfx/ots/src/sile.cc b/gfx/ots/src/sile.cc new file mode 100644 index 0000000000..9b34f8aad6 --- /dev/null +++ b/gfx/ots/src/sile.cc @@ -0,0 +1,71 @@ +// 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) { + 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 0000000000..bdb00606f6 --- /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 <vector> + +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<uint16_t> fontName; + uint16_t fontFileLength; + std::vector<uint16_t> 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 0000000000..e1d521a98c --- /dev/null +++ b/gfx/ots/src/silf.cc @@ -0,0 +1,976 @@ +// 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 <cmath> +#include <memory> + +namespace ots { + +bool OpenTypeSILF::Parse(const uint8_t* data, size_t length, + bool prevent_decompression) { + 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 <= OTS_MAX_DECOMPRESSED_TABLE_SIZE + if (decompressed_size > OTS_MAX_DECOMPRESSED_TABLE_SIZE) { + return DropGraphite("Decompressed size exceeds %gMB: %gMB", + OTS_MAX_DECOMPRESSED_TABLE_SIZE / (1024.0 * 1024.0), + decompressed_size / (1024.0 * 1024.0)); + } + std::unique_ptr<uint8_t> decompressed(new uint8_t[decompressed_size]()); + size_t outputSize = 0; + bool ret = mozilla::Compression::LZ4::decompressPartial( + reinterpret_cast<const char*>(data + table.offset()), + table.remaining(), // input buffer size (input size + padding) + reinterpret_cast<char*>(decompressed.get()), + decompressed_size, // target output size + &outputSize); // return output size + if (!ret || outputSize != decompressed_size) { + return DropGraphite("Decompression failed"); + } + return this->Parse(decompressed.get(), 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->attrCollisions)) { + return parent->Error("SILSub: Failed to read attrCollisions"); + } + if (parent->version < 0x40001 && this->attrCollisions != 0) { + parent->Warning("SILSub: Nonzero attrCollisions (reserved before v4.1)"); + } + 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<unsigned>(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->attrCollisions) || + !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<uint16_t>(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<unsigned long>(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<uint32_t>(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<uint16_t>(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<unsigned long>(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<unsigned long>(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<unsigned long>(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<OpenTypeNAME*>( + 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 0000000000..5980a0ab24 --- /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 <vector> + +#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<OpenTypeSILF> { + explicit SILSub(OpenTypeSILF* parent) + : TablePart<OpenTypeSILF>(parent), classes(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + struct JustificationLevel : public TablePart<OpenTypeSILF> { + explicit JustificationLevel(OpenTypeSILF* parent) + : TablePart<OpenTypeSILF>(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<OpenTypeSILF> { + explicit PseudoMap(OpenTypeSILF* parent) + : TablePart<OpenTypeSILF>(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + uint32_t unicode; + uint16_t nPseudo; + }; + struct ClassMap : public TablePart<OpenTypeSILF> { + explicit ClassMap(OpenTypeSILF* parent) + : TablePart<OpenTypeSILF>(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + struct LookupClass : public TablePart<OpenTypeSILF> { + explicit LookupClass(OpenTypeSILF* parent) + : TablePart<OpenTypeSILF>(parent) { } + bool ParsePart(Buffer& table); + bool SerializePart(OTSStream* out) const; + struct LookupPair : public TablePart<OpenTypeSILF> { + explicit LookupPair(OpenTypeSILF* parent) + : TablePart<OpenTypeSILF>(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<LookupPair> lookups; + }; + uint16_t numClass; + uint16_t numLinear; + std::vector<uint32_t> oClass; // uint16_t before v4 + std::vector<uint16_t> glyphs; + std::vector<LookupClass> lookups; + }; + struct SILPass : public TablePart<OpenTypeSILF> { + explicit SILPass(OpenTypeSILF* parent) + : TablePart<OpenTypeSILF>(parent) { } + bool ParsePart(Buffer& table OTS_UNUSED) { 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<OpenTypeSILF> { + explicit PassRange(OpenTypeSILF* parent) + : TablePart<OpenTypeSILF>(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<PassRange> ranges; + std::vector<uint16_t> oRuleMap; + std::vector<uint16_t> ruleMap; + uint8_t minRulePreContext; + uint8_t maxRulePreContext; + std::vector<int16_t> startStates; + std::vector<uint16_t> ruleSortKeys; + std::vector<uint8_t> rulePreContext; + uint8_t collisionThreshold; // reserved before v5 + uint16_t pConstraint; + std::vector<uint16_t> oConstraints; + std::vector<uint16_t> oActions; + std::vector<std::vector<uint16_t>> stateTrans; + uint8_t reserved2; + std::vector<uint8_t> passConstraints; + std::vector<uint8_t> ruleConstraints; + std::vector<uint8_t> actions; + std::vector<uint16_t> dActions; + std::vector<uint16_t> dStates; + std::vector<uint16_t> 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<JustificationLevel> jLevels; + uint16_t numLigComp; + uint8_t numUserDefn; + uint8_t maxCompPerLig; + uint8_t direction; + uint8_t attrCollisions; // reserved3 before v4.1 + uint8_t reserved4; + uint8_t reserved5; + uint8_t reserved6; + uint8_t numCritFeatures; + std::vector<uint16_t> critFeatures; + uint8_t reserved7; + uint8_t numScriptTag; + std::vector<uint32_t> scriptTag; + uint16_t lbGID; + std::vector<uint32_t> oPasses; + uint16_t numPseudo; + uint16_t searchPseudo; + uint16_t pseudoSelector; + uint16_t pseudoShift; + std::vector<PseudoMap> pMaps; + ClassMap classes; + std::vector<SILPass> 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<uint32_t> offset; + std::vector<SILSub> 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 0000000000..13f47e1b6b --- /dev/null +++ b/gfx/ots/src/sill.cc @@ -0,0 +1,156 @@ +// 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 <cmath> +#include <unordered_set> + +namespace ots { + +bool OpenTypeSILL::Parse(const uint8_t* data, size_t length) { + 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<size_t> unverified; + //this->entries.resize(static_cast<unsigned long>(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<OpenTypeFEAT*>( + 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 0000000000..30f9b8d83a --- /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 <vector> + +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<OpenTypeSILL> { + explicit LanguageEntry(OpenTypeSILL* parent) + : TablePart<OpenTypeSILL>(parent) { } + bool ParsePart(Buffer &table); + bool SerializePart(OTSStream* out) const; + uint8_t langcode[4]; + uint16_t numSettings; + uint16_t offset; + }; + struct LangFeatureSetting : public TablePart<OpenTypeSILL> { + explicit LangFeatureSetting(OpenTypeSILL* parent) + : TablePart<OpenTypeSILL>(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<LanguageEntry> entries; + std::vector<LangFeatureSetting> 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 0000000000..f6f65fdf60 --- /dev/null +++ b/gfx/ots/src/stat.cc @@ -0,0 +1,341 @@ +// 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) { + OpenTypeNAME* name = static_cast<OpenTypeNAME*>( + GetFont()->GetTypedTable(OTS_TAG_NAME)); + + if (!name || !name->IsValidNameId(nameid)) { + Drop("Invalid nameID: %d", nameid); + return false; + } + + 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)) { + 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) { + return Drop("Invalid table minorVersion for format 4 axis values: %d", this->minorVersion); + } + 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 0000000000..5fd7108564 --- /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 <vector> + +#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); + + 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<AxisRecord> 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<AxisValue> 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<AxisValueRecord> 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 0000000000..55afd976ca --- /dev/null +++ b/gfx/ots/src/variations.cc @@ -0,0 +1,271 @@ +// 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<ots::OpenTypeFVAR*>(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 wordDeltaCount; + + const uint16_t LONG_WORDS = 0x8000u; + const uint16_t WORD_DELTA_COUNT_MASK = 0x7FFF; + + if (!subtable.ReadU16(&itemCount) || + !subtable.ReadU16(&wordDeltaCount) || + !subtable.ReadU16(regionIndexCount)) { + return OTS_FAILURE_MSG("Failed to read variation data subtable header"); + } + + size_t valueSize = (wordDeltaCount & LONG_WORDS) ? 2 : 1; + wordDeltaCount &= WORD_DELTA_COUNT_MASK; + + if (wordDeltaCount > *regionIndexCount) { + return OTS_FAILURE_MSG("Bad word delta count"); + } + + 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(valueSize * size_t(itemCount) * (size_t(wordDeltaCount) + 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<uint16_t>* 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("Tuple coordinate not in the range [-1.0, 1.0]: %g", coordinate / 16384.); + } + } + } + + if (tupleIndex & INTERMEDIATE_REGION) { + std::vector<int16_t> 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("Tuple coordinate not in the range [-1.0, 1.0]: %g", coordinate / 16384.); + } + startTuple.push_back(coordinate); + } + + std::vector<int16_t> 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("Tuple coordinate not in the range [-1.0, 1.0]: %g", coordinate / 16384.); + } + 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 0000000000..aaaac17844 --- /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 <vector> +#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<uint16_t>* 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 new file mode 100644 index 0000000000..17433f8894 --- /dev/null +++ b/gfx/ots/src/vdmx.cc @@ -0,0 +1,173 @@ +// 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 "vdmx.h" + +#include <set> + +// VDMX - Vertical Device Metrics +// http://www.microsoft.com/typography/otspec/vdmx.htm + +namespace ots { + +#define TABLE_NAME "VDMX" + +bool OpenTypeVDMX::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + ots::Font* font = this->GetFont(); + + if (!table.ReadU16(&this->version) || + !table.ReadU16(&this->num_recs) || + !table.ReadU16(&this->num_ratios)) { + return Drop("Failed to read table header"); + } + + if (this->version > 1) { + return Drop("Unsupported table version: %u", this->version); + } + + 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 Drop("Failed to read RatioRange record %d", i); + } + + if (rec.charset > 1) { + return Drop("Unsupported character set: %u", rec.charset); + } + + if (rec.y_start_ratio > rec.y_end_ratio) { + 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 < 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. + return Drop("Superfluous terminator found"); + } + + this->rat_ranges.push_back(rec); + } + + this->offsets.reserve(this->num_ratios); + const size_t current_offset = table.offset(); + std::set<uint16_t> unique_offsets; + // current_offset is less than (2 bytes * 3) + (4 bytes * USHRT_MAX) = 256k. + for (unsigned i = 0; i < this->num_ratios; ++i) { + uint16_t offset; + if (!table.ReadU16(&offset)) { + return Drop("Failed to read ratio offset %d", i); + } + if (current_offset + offset >= length) { // thus doesn't overflow. + return Drop("Bad ratio offset %d for ration %d", offset, i); + } + + this->offsets.push_back(offset); + unique_offsets.insert(offset); + } + + // Check that num_recs is sufficient to provide as many VDMXGroup records + // as there are unique offsets; if not, update it (we'll return an error + // below if they're not actually present). + if (unique_offsets.size() > this->num_recs) { + OTS_WARNING("increasing num_recs (%u is too small for %u unique offsets)", + this->num_recs, unique_offsets.size()); + this->num_recs = unique_offsets.size(); + } + + 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 Drop("Failed to read record header %d", i); + } + group.entries.reserve(group.recs); + for (unsigned j = 0; j < group.recs; ++j) { + OpenTypeVDMXVTable vt; + if (!table.ReadU16(&vt.y_pel_height) || + !table.ReadS16(&vt.y_max) || + !table.ReadS16(&vt.y_min)) { + return Drop("Failed to read record %d group %d", i, j); + } + if (vt.y_max < vt.y_min) { + 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)) { + return Drop("The table is not sorted"); + } + + group.entries.push_back(vt); + } + this->groups.push_back(group); + } + + return true; +} + +bool OpenTypeVDMX::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for CFF fonts. + GetFont()->GetTable(OTS_TAG_GLYF) != NULL; +} + +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 < 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 Error("Failed to write RatioRange record %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 < 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 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 Error("Failed to write group %d entry %d", i, j); + } + } + } + + return true; +} + +#undef TABLE_NAME + +} // namespace ots diff --git a/gfx/ots/src/vdmx.h b/gfx/ots/src/vdmx.h new file mode 100644 index 0000000000..6ccf6dc1b6 --- /dev/null +++ b/gfx/ots/src/vdmx.h @@ -0,0 +1,54 @@ +// 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_VDMX_H_ +#define OTS_VDMX_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeVDMXRatioRecord { + uint8_t charset; + uint8_t x_ratio; + uint8_t y_start_ratio; + uint8_t y_end_ratio; +}; + +struct OpenTypeVDMXVTable { + uint16_t y_pel_height; + int16_t y_max; + int16_t y_min; +}; + +struct OpenTypeVDMXGroup { + uint16_t recs; + uint8_t startsz; + uint8_t endsz; + std::vector<OpenTypeVDMXVTable> entries; +}; + +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; + std::vector<OpenTypeVDMXRatioRecord> rat_ranges; + std::vector<uint16_t> offsets; + std::vector<OpenTypeVDMXGroup> groups; +}; + +} // namespace ots + +#endif // OTS_VDMX_H_ diff --git a/gfx/ots/src/vhea.cc b/gfx/ots/src/vhea.cc new file mode 100644 index 0000000000..cd2af04b04 --- /dev/null +++ b/gfx/ots/src/vhea.cc @@ -0,0 +1,29 @@ +// 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 "vhea.h" + +#include "head.h" +#include "maxp.h" + +// vhea - Vertical Header Table +// http://www.microsoft.com/typography/otspec/vhea.htm + +namespace ots { + +bool OpenTypeVHEA::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (!table.ReadU32(&this->version)) { + return Error("Failed to read version"); + } + if (this->version != 0x00010000 && + this->version != 0x00011000) { + return Error("Unsupported table version: 0x%x", this->version); + } + + return OpenTypeMetricsHeader::Parse(data, length); +} + +} // namespace ots diff --git a/gfx/ots/src/vhea.h b/gfx/ots/src/vhea.h new file mode 100644 index 0000000000..132ec4e08c --- /dev/null +++ b/gfx/ots/src/vhea.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef OTS_VHEA_H_ +#define OTS_VHEA_H_ + +#include "metrics.h" +#include "ots.h" + +namespace ots { + +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); +}; + +} // namespace ots + +#endif // OTS_VHEA_H_ + diff --git a/gfx/ots/src/vmtx.h b/gfx/ots/src/vmtx.h new file mode 100644 index 0000000000..a26d775420 --- /dev/null +++ b/gfx/ots/src/vmtx.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef OTS_VMTX_H_ +#define OTS_VMTX_H_ + +#include "metrics.h" +#include "vhea.h" +#include "ots.h" + +namespace ots { + +struct OpenTypeVMTX : public OpenTypeMetricsTable { + public: + explicit OpenTypeVMTX(Font *font, uint32_t tag) + : OpenTypeMetricsTable(font, tag, tag, OTS_TAG_VHEA) { } +}; + +} // namespace ots + +#endif // OTS_VMTX_H_ + diff --git a/gfx/ots/src/vorg.cc b/gfx/ots/src/vorg.cc new file mode 100644 index 0000000000..3b4b51c533 --- /dev/null +++ b/gfx/ots/src/vorg.cc @@ -0,0 +1,83 @@ +// 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 "vorg.h" + +#include <vector> + +// VORG - Vertical Origin Table +// http://www.microsoft.com/typography/otspec/vorg.htm + +namespace ots { + +bool OpenTypeVORG::Parse(const uint8_t *data, size_t length) { + Buffer table(data, length); + + uint16_t num_recs; + if (!table.ReadU16(&this->major_version) || + !table.ReadU16(&this->minor_version) || + !table.ReadS16(&this->default_vert_origin_y) || + !table.ReadU16(&num_recs)) { + return Error("Failed to read header"); + } + if (this->major_version != 1) { + return Drop("Unsupported majorVersion: %u", this->major_version); + } + if (this->minor_version != 0) { + return Drop("Unsupported minorVersion: %u", this->minor_version); + } + + // num_recs might be zero (e.g., DFHSMinchoPro5-W3-Demo.otf). + if (!num_recs) { + return true; + } + + uint16_t last_glyph_index = 0; + 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 Error("Failed to read record %d", i); + } + if ((i != 0) && (rec.glyph_index <= last_glyph_index)) { + return Drop("The table is not sorted"); + } + last_glyph_index = rec.glyph_index; + + this->metrics.push_back(rec); + } + + return true; +} + +bool OpenTypeVORG::Serialize(OTSStream *out) { + const uint16_t num_metrics = static_cast<uint16_t>(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 Error("Failed to write table header"); + } + + for (uint16_t i = 0; i < num_metrics; ++i) { + const OpenTypeVORGMetrics& rec = this->metrics[i]; + if (!out->WriteU16(rec.glyph_index) || + !out->WriteS16(rec.vert_origin_y)) { + return Error("Failed to write record %d", i); + } + } + + return true; +} + +bool OpenTypeVORG::ShouldSerialize() { + return Table::ShouldSerialize() && + // this table is not for fonts with TT glyphs. + GetFont()->GetTable(OTS_TAG_CFF) != NULL; +} + +} // namespace ots diff --git a/gfx/ots/src/vorg.h b/gfx/ots/src/vorg.h new file mode 100644 index 0000000000..caffea8753 --- /dev/null +++ b/gfx/ots/src/vorg.h @@ -0,0 +1,37 @@ +// 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_VORG_H_ +#define OTS_VORG_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeVORGMetrics { + uint16_t glyph_index; + int16_t vert_origin_y; +}; + +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; + std::vector<OpenTypeVORGMetrics> metrics; +}; + +} // namespace ots + +#endif // OTS_VORG_H_ diff --git a/gfx/ots/src/vvar.cc b/gfx/ots/src/vvar.cc new file mode 100644 index 0000000000..b47ea479da --- /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 0000000000..15d4357459 --- /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/tests/cff_charstring_test.cc b/gfx/ots/tests/cff_charstring_test.cc new file mode 100644 index 0000000000..18e077e8f0 --- /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 <gtest/gtest.h> + +#include <climits> +#include <vector> + +#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<uint8_t> *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<uint8_t> *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<uint8_t>* 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<uint8_t> 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))); + } +} |