summaryrefslogtreecommitdiffstats
path: root/gfx/ots/src/cpal.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/ots/src/cpal.cc')
-rw-r--r--gfx/ots/src/cpal.cc285
1 files changed, 285 insertions, 0 deletions
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