summaryrefslogtreecommitdiffstats
path: root/gfx/ots
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/ots
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--gfx/ots/LICENSE27
-rw-r--r--gfx/ots/RLBoxWOFF2Host.cpp234
-rw-r--r--gfx/ots/RLBoxWOFF2Host.h59
-rw-r--r--gfx/ots/RLBoxWOFF2Types.h37
-rw-r--r--gfx/ots/include/opentype-sanitiser.h228
-rw-r--r--gfx/ots/include/ots-memory-stream.h109
-rw-r--r--gfx/ots/moz.yaml42
-rw-r--r--gfx/ots/ots-1850314.patch177
-rw-r--r--gfx/ots/ots-lz4.patch74
-rw-r--r--gfx/ots/ots-rlbox.patch60
-rw-r--r--gfx/ots/ots-visibility.patch43
-rw-r--r--gfx/ots/src/avar.cc164
-rw-r--r--gfx/ots/src/avar.h46
-rw-r--r--gfx/ots/src/cff.cc1381
-rw-r--r--gfx/ots/src/cff.h98
-rw-r--r--gfx/ots/src/cff_charstring.cc1020
-rw-r--r--gfx/ots/src/cff_charstring.h110
-rw-r--r--gfx/ots/src/cmap.cc1076
-rw-r--r--gfx/ots/src/cmap.h87
-rw-r--r--gfx/ots/src/colr.cc1092
-rw-r--r--gfx/ots/src/colr.h31
-rw-r--r--gfx/ots/src/cpal.cc285
-rw-r--r--gfx/ots/src/cpal.h40
-rw-r--r--gfx/ots/src/cvar.cc56
-rw-r--r--gfx/ots/src/cvar.h31
-rw-r--r--gfx/ots/src/cvt.cc46
-rw-r--r--gfx/ots/src/cvt.h28
-rw-r--r--gfx/ots/src/feat.cc190
-rw-r--r--gfx/ots/src/feat.h61
-rw-r--r--gfx/ots/src/fpgm.cc42
-rw-r--r--gfx/ots/src/fpgm.h28
-rw-r--r--gfx/ots/src/fvar.cc164
-rw-r--r--gfx/ots/src/fvar.h63
-rw-r--r--gfx/ots/src/gasp.cc84
-rw-r--r--gfx/ots/src/gasp.h32
-rw-r--r--gfx/ots/src/gdef.cc364
-rw-r--r--gfx/ots/src/gdef.h40
-rw-r--r--gfx/ots/src/glat.cc458
-rw-r--r--gfx/ots/src/glat.h172
-rw-r--r--gfx/ots/src/gloc.cc105
-rw-r--r--gfx/ots/src/gloc.h36
-rw-r--r--gfx/ots/src/glyf.cc632
-rw-r--r--gfx/ots/src/glyf.h82
-rw-r--r--gfx/ots/src/gpos.cc711
-rw-r--r--gfx/ots/src/gpos.h35
-rw-r--r--gfx/ots/src/graphite.h96
-rw-r--r--gfx/ots/src/gsub.cc533
-rw-r--r--gfx/ots/src/gsub.h34
-rw-r--r--gfx/ots/src/gvar.cc209
-rw-r--r--gfx/ots/src/gvar.h43
-rw-r--r--gfx/ots/src/hdmx.cc120
-rw-r--r--gfx/ots/src/hdmx.h38
-rw-r--r--gfx/ots/src/head.cc132
-rw-r--r--gfx/ots/src/head.h36
-rw-r--r--gfx/ots/src/hhea.cc28
-rw-r--r--gfx/ots/src/hhea.h23
-rw-r--r--gfx/ots/src/hmtx.h22
-rw-r--r--gfx/ots/src/hvar.cc85
-rw-r--r--gfx/ots/src/hvar.h31
-rw-r--r--gfx/ots/src/kern.cc177
-rw-r--r--gfx/ots/src/kern.h49
-rw-r--r--gfx/ots/src/layout.cc1735
-rw-r--r--gfx/ots/src/layout.h65
-rw-r--r--gfx/ots/src/loca.cc93
-rw-r--r--gfx/ots/src/loca.h27
-rw-r--r--gfx/ots/src/ltsh.cc71
-rw-r--r--gfx/ots/src/ltsh.h30
-rw-r--r--gfx/ots/src/math.cc584
-rw-r--r--gfx/ots/src/math_.h69
-rw-r--r--gfx/ots/src/maxp.cc103
-rw-r--r--gfx/ots/src/maxp.h42
-rw-r--r--gfx/ots/src/metrics.cc177
-rw-r--r--gfx/ots/src/metrics.h56
-rw-r--r--gfx/ots/src/moz.build84
-rw-r--r--gfx/ots/src/mvar.cc107
-rw-r--r--gfx/ots/src/mvar.h31
-rw-r--r--gfx/ots/src/name.cc409
-rw-r--r--gfx/ots/src/name.h65
-rw-r--r--gfx/ots/src/os2.cc320
-rw-r--r--gfx/ots/src/os2.h67
-rw-r--r--gfx/ots/src/ots.cc1151
-rw-r--r--gfx/ots/src/ots.h364
-rw-r--r--gfx/ots/src/post.cc177
-rw-r--r--gfx/ots/src/post.h37
-rw-r--r--gfx/ots/src/prep.cc43
-rw-r--r--gfx/ots/src/prep.h28
-rw-r--r--gfx/ots/src/sile.cc71
-rw-r--r--gfx/ots/src/sile.h36
-rw-r--r--gfx/ots/src/silf.cc976
-rw-r--r--gfx/ots/src/silf.h196
-rw-r--r--gfx/ots/src/sill.cc156
-rw-r--r--gfx/ots/src/sill.h53
-rw-r--r--gfx/ots/src/stat.cc341
-rw-r--r--gfx/ots/src/stat.h155
-rw-r--r--gfx/ots/src/variations.cc271
-rw-r--r--gfx/ots/src/variations.h26
-rw-r--r--gfx/ots/src/vdmx.cc173
-rw-r--r--gfx/ots/src/vdmx.h54
-rw-r--r--gfx/ots/src/vhea.cc29
-rw-r--r--gfx/ots/src/vhea.h24
-rw-r--r--gfx/ots/src/vmtx.h23
-rw-r--r--gfx/ots/src/vorg.cc83
-rw-r--r--gfx/ots/src/vorg.h37
-rw-r--r--gfx/ots/src/vvar.cc95
-rw-r--r--gfx/ots/src/vvar.h31
-rw-r--r--gfx/ots/tests/cff_charstring_test.cc1588
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(&centerX) ||
+ !subtable.ReadS16(&centerY) ||
+ !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(&centerX) ||
+ !subtable.ReadS16(&centerY))) ||
+ (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(&centerX) ||
+ !subtable.ReadS16(&centerY))) ||
+ (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(&centerX) ||
+ !subtable.ReadS16(&centerY))) ||
+ (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(&regionIndex) || 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,
+ &regionCount)) {
+ 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,
+ &regionIndexCount)) {
+ 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)));
+ }
+}