summaryrefslogtreecommitdiffstats
path: root/gfx/ots/src/stat.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/ots/src/stat.cc')
-rw-r--r--gfx/ots/src/stat.cc347
1 files changed, 347 insertions, 0 deletions
diff --git a/gfx/ots/src/stat.cc b/gfx/ots/src/stat.cc
new file mode 100644
index 000000000..9b7828109
--- /dev/null
+++ b/gfx/ots/src/stat.cc
@@ -0,0 +1,347 @@
+// Copyright (c) 2018 The OTS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "stat.h"
+#include "name.h"
+
+namespace ots {
+
+// -----------------------------------------------------------------------------
+// OpenTypeSTAT
+// -----------------------------------------------------------------------------
+
+bool OpenTypeSTAT::ValidateNameId(uint16_t nameid, bool allowPredefined) {
+ OpenTypeNAME* name = static_cast<OpenTypeNAME*>(
+ GetFont()->GetTypedTable(OTS_TAG_NAME));
+
+ if (!name || !name->IsValidNameId(nameid)) {
+ Drop("Invalid nameID: %d", nameid);
+ return false;
+ }
+
+ if (!allowPredefined && nameid < 26) {
+ Warning("nameID out of range: %d", nameid);
+ return true;
+ }
+
+ if ((nameid >= 26 && nameid <= 255) || nameid >= 32768) {
+ Warning("nameID out of range: %d", nameid);
+ return true;
+ }
+
+ return true;
+}
+
+bool OpenTypeSTAT::Parse(const uint8_t* data, size_t length) {
+ Buffer table(data, length);
+ if (!table.ReadU16(&this->majorVersion) ||
+ !table.ReadU16(&this->minorVersion) ||
+ !table.ReadU16(&this->designAxisSize) ||
+ !table.ReadU16(&this->designAxisCount) ||
+ !table.ReadU32(&this->designAxesOffset) ||
+ !table.ReadU16(&this->axisValueCount) ||
+ !table.ReadU32(&this->offsetToAxisValueOffsets) ||
+ !(this->minorVersion < 1 || table.ReadU16(&this->elidedFallbackNameID))) {
+ return Drop("Failed to read table header");
+ }
+ if (this->majorVersion != 1) {
+ return Drop("Unknown table version");
+ }
+ if (this->minorVersion > 2) {
+ Warning("Unknown minor version, downgrading to 2");
+ this->minorVersion = 2;
+ }
+
+ if (this->designAxisSize < sizeof(AxisRecord)) {
+ return Drop("Invalid designAxisSize");
+ }
+
+ size_t headerEnd = table.offset();
+
+ if (this->designAxisCount == 0) {
+ if (this->designAxesOffset != 0) {
+ Warning("Unexpected non-zero designAxesOffset");
+ this->designAxesOffset = 0;
+ }
+ } else {
+ if (this->designAxesOffset < headerEnd ||
+ size_t(this->designAxesOffset) +
+ size_t(this->designAxisCount) * size_t(this->designAxisSize) > length) {
+ return Drop("Invalid designAxesOffset");
+ }
+ }
+
+ for (size_t i = 0; i < this->designAxisCount; i++) {
+ table.set_offset(this->designAxesOffset + i * this->designAxisSize);
+ this->designAxes.emplace_back();
+ auto& axis = this->designAxes[i];
+ if (!table.ReadU32(&axis.axisTag) ||
+ !table.ReadU16(&axis.axisNameID) ||
+ !table.ReadU16(&axis.axisOrdering)) {
+ return Drop("Failed to read design axis");
+ }
+ if (!CheckTag(axis.axisTag)) {
+ return Drop("Bad design axis tag");
+ }
+ if (!ValidateNameId(axis.axisNameID, false)) {
+ return true;
+ }
+ }
+
+ // TODO
+ // - check that all axes defined in fvar are covered by STAT
+ // - check that axisOrdering values are not duplicated (warn only)
+
+ if (this->axisValueCount == 0) {
+ if (this->offsetToAxisValueOffsets != 0) {
+ Warning("Unexpected non-zero offsetToAxisValueOffsets");
+ this->offsetToAxisValueOffsets = 0;
+ }
+ } else {
+ if (this->offsetToAxisValueOffsets < headerEnd ||
+ size_t(this->offsetToAxisValueOffsets) +
+ size_t(this->axisValueCount) * sizeof(uint16_t) > length) {
+ return Drop("Invalid offsetToAxisValueOffsets");
+ }
+ }
+
+ for (size_t i = 0; i < this->axisValueCount; i++) {
+ table.set_offset(this->offsetToAxisValueOffsets + i * sizeof(uint16_t));
+ uint16_t axisValueOffset;
+ if (!table.ReadU16(&axisValueOffset)) {
+ return Drop("Failed to read axis value offset");
+ }
+ if (this->offsetToAxisValueOffsets + axisValueOffset > length) {
+ return Drop("Invalid axis value offset");
+ }
+ table.set_offset(this->offsetToAxisValueOffsets + axisValueOffset);
+ uint16_t format;
+ if (!table.ReadU16(&format)) {
+ return Drop("Failed to read axis value format");
+ }
+ this->axisValues.emplace_back(format);
+ auto& axisValue = axisValues[i];
+ switch (format) {
+ case 1:
+ if (!table.ReadU16(&axisValue.format1.axisIndex) ||
+ !table.ReadU16(&axisValue.format1.flags) ||
+ !table.ReadU16(&axisValue.format1.valueNameID) ||
+ !table.ReadS32(&axisValue.format1.value)) {
+ return Drop("Failed to read axis value (format 1)");
+ }
+ if (axisValue.format1.axisIndex >= this->designAxisCount) {
+ return Drop("Axis index out of range");
+ }
+ if ((axisValue.format1.flags & 0xFFFCu) != 0) {
+ Warning("Unexpected axis value flags");
+ axisValue.format1.flags &= ~0xFFFCu;
+ }
+ if (!ValidateNameId(axisValue.format1.valueNameID)) {
+ return true;
+ }
+ break;
+ case 2:
+ if (!table.ReadU16(&axisValue.format2.axisIndex) ||
+ !table.ReadU16(&axisValue.format2.flags) ||
+ !table.ReadU16(&axisValue.format2.valueNameID) ||
+ !table.ReadS32(&axisValue.format2.nominalValue) ||
+ !table.ReadS32(&axisValue.format2.rangeMinValue) ||
+ !table.ReadS32(&axisValue.format2.rangeMaxValue)) {
+ return Drop("Failed to read axis value (format 2)");
+ }
+ if (axisValue.format2.axisIndex >= this->designAxisCount) {
+ return Drop("Axis index out of range");
+ }
+ if ((axisValue.format2.flags & 0xFFFCu) != 0) {
+ Warning("Unexpected axis value flags");
+ axisValue.format1.flags &= ~0xFFFCu;
+ }
+ if (!ValidateNameId(axisValue.format2.valueNameID)) {
+ return true;
+ }
+ if (!(axisValue.format2.rangeMinValue <= axisValue.format2.nominalValue &&
+ axisValue.format2.nominalValue <= axisValue.format2.rangeMaxValue)) {
+ Warning("Bad axis value range or nominal value");
+ }
+ break;
+ case 3:
+ if (!table.ReadU16(&axisValue.format3.axisIndex) ||
+ !table.ReadU16(&axisValue.format3.flags) ||
+ !table.ReadU16(&axisValue.format3.valueNameID) ||
+ !table.ReadS32(&axisValue.format3.value) ||
+ !table.ReadS32(&axisValue.format3.linkedValue)) {
+ return Drop("Failed to read axis value (format 3)");
+ }
+ if (axisValue.format3.axisIndex >= this->designAxisCount) {
+ return Drop("Axis index out of range");
+ }
+ if ((axisValue.format3.flags & 0xFFFCu) != 0) {
+ Warning("Unexpected axis value flags");
+ axisValue.format3.flags &= ~0xFFFCu;
+ }
+ if (!ValidateNameId(axisValue.format3.valueNameID)) {
+ return true;
+ }
+ break;
+ case 4:
+ if (this->minorVersion < 2) {
+ Warning("Invalid table version for format 4 axis values - updating");
+ this->minorVersion = 2;
+ }
+ if (!table.ReadU16(&axisValue.format4.axisCount) ||
+ !table.ReadU16(&axisValue.format4.flags) ||
+ !table.ReadU16(&axisValue.format4.valueNameID)) {
+ return Drop("Failed to read axis value (format 4)");
+ }
+ if (axisValue.format4.axisCount > this->designAxisCount) {
+ return Drop("Axis count out of range");
+ }
+ if ((axisValue.format4.flags & 0xFFFCu) != 0) {
+ Warning("Unexpected axis value flags");
+ axisValue.format4.flags &= ~0xFFFCu;
+ }
+ if (!ValidateNameId(axisValue.format4.valueNameID)) {
+ return true;
+ }
+ for (unsigned j = 0; j < axisValue.format4.axisCount; j++) {
+ axisValue.format4.axisValues.emplace_back();
+ auto& v = axisValue.format4.axisValues[j];
+ if (!table.ReadU16(&v.axisIndex) ||
+ !table.ReadS32(&v.value)) {
+ return Drop("Failed to read axis value");
+ }
+ if (v.axisIndex >= this->designAxisCount) {
+ return Drop("Axis index out of range");
+ }
+ }
+ break;
+ default:
+ return Drop("Unknown axis value format");
+ }
+ }
+
+ return true;
+}
+
+bool OpenTypeSTAT::Serialize(OTSStream* out) {
+ off_t tableStart = out->Tell();
+
+ size_t headerSize = 5 * sizeof(uint16_t) + 2 * sizeof(uint32_t);
+ if (this->minorVersion >= 1) {
+ headerSize += sizeof(uint16_t);
+ }
+
+ if (this->designAxisCount == 0) {
+ this->designAxesOffset = 0;
+ } else {
+ this->designAxesOffset = headerSize;
+ }
+
+ this->designAxisSize = sizeof(AxisRecord);
+
+ if (this->axisValueCount == 0) {
+ this->offsetToAxisValueOffsets = 0;
+ } else {
+ if (this->designAxesOffset == 0) {
+ this->offsetToAxisValueOffsets = headerSize;
+ } else {
+ this->offsetToAxisValueOffsets = this->designAxesOffset + this->designAxisCount * this->designAxisSize;
+ }
+ }
+
+ if (!out->WriteU16(this->majorVersion) ||
+ !out->WriteU16(this->minorVersion) ||
+ !out->WriteU16(this->designAxisSize) ||
+ !out->WriteU16(this->designAxisCount) ||
+ !out->WriteU32(this->designAxesOffset) ||
+ !out->WriteU16(this->axisValueCount) ||
+ !out->WriteU32(this->offsetToAxisValueOffsets) ||
+ !(this->minorVersion < 1 || out->WriteU16(this->elidedFallbackNameID))) {
+ return Error("Failed to write table header");
+ }
+
+ if (this->designAxisCount > 0) {
+ if (out->Tell() - tableStart != this->designAxesOffset) {
+ return Error("Error computing designAxesOffset");
+ }
+ }
+
+ for (unsigned i = 0; i < this->designAxisCount; i++) {
+ const auto& axis = this->designAxes[i];
+ if (!out->WriteU32(axis.axisTag) ||
+ !out->WriteU16(axis.axisNameID) ||
+ !out->WriteU16(axis.axisOrdering)) {
+ return Error("Failed to write design axis");
+ }
+ }
+
+ if (this->axisValueCount > 0) {
+ if (out->Tell() - tableStart != this->offsetToAxisValueOffsets) {
+ return Error("Error computing offsetToAxisValueOffsets");
+ }
+ }
+
+ uint32_t axisValueOffset = this->axisValueCount * sizeof(uint16_t);
+ for (unsigned i = 0; i < this->axisValueCount; i++) {
+ const auto& value = this->axisValues[i];
+ if (!out->WriteU16(axisValueOffset)) {
+ return Error("Failed to write axis value offset");
+ }
+ axisValueOffset += value.Length();
+ }
+ for (unsigned i = 0; i < this->axisValueCount; i++) {
+ const auto& value = this->axisValues[i];
+ if (!out->WriteU16(value.format)) {
+ return Error("Failed to write axis value");
+ }
+ switch (value.format) {
+ case 1:
+ if (!out->WriteU16(value.format1.axisIndex) ||
+ !out->WriteU16(value.format1.flags) ||
+ !out->WriteU16(value.format1.valueNameID) ||
+ !out->WriteS32(value.format1.value)) {
+ return Error("Failed to write axis value");
+ }
+ break;
+ case 2:
+ if (!out->WriteU16(value.format2.axisIndex) ||
+ !out->WriteU16(value.format2.flags) ||
+ !out->WriteU16(value.format2.valueNameID) ||
+ !out->WriteS32(value.format2.nominalValue) ||
+ !out->WriteS32(value.format2.rangeMinValue) ||
+ !out->WriteS32(value.format2.rangeMaxValue)) {
+ return Error("Failed to write axis value");
+ }
+ break;
+ case 3:
+ if (!out->WriteU16(value.format3.axisIndex) ||
+ !out->WriteU16(value.format3.flags) ||
+ !out->WriteU16(value.format3.valueNameID) ||
+ !out->WriteS32(value.format3.value) ||
+ !out->WriteS32(value.format3.linkedValue)) {
+ return Error("Failed to write axis value");
+ }
+ break;
+ case 4:
+ if (!out->WriteU16(value.format4.axisCount) ||
+ !out->WriteU16(value.format4.flags) ||
+ !out->WriteU16(value.format4.valueNameID)) {
+ return Error("Failed to write axis value");
+ }
+ for (unsigned j = 0; j < value.format4.axisValues.size(); j++) {
+ if (!out->WriteU16(value.format4.axisValues[j].axisIndex) ||
+ !out->WriteS32(value.format4.axisValues[j].value)) {
+ return Error("Failed to write axis value");
+ }
+ }
+ break;
+ default:
+ return Error("Bad value format");
+ }
+ }
+
+ return true;
+}
+
+} // namespace ots