| // 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) { |
| 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 |