cpal.cc (9145B)
1 // Copyright (c) 2022 The OTS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "cpal.h" 6 #include "name.h" 7 8 // CPAL - Color Palette Table 9 // http://www.microsoft.com/typography/otspec/cpal.htm 10 11 #define TABLE_NAME "CPAL" 12 13 namespace { 14 15 // Caller has sized the colorRecords array, so we know how much to try and read. 16 bool ParseColorRecordsArray(const ots::Font* font, 17 const uint8_t* data, size_t length, 18 std::vector<uint32_t>* colorRecords) 19 { 20 ots::Buffer subtable(data, length); 21 22 for (auto& color : *colorRecords) { 23 if (!subtable.ReadU32(&color)) { 24 return OTS_FAILURE_MSG("Failed to read color record"); 25 } 26 } 27 28 return true; 29 } 30 31 // Caller has sized the paletteTypes array, so we know how much to try and read. 32 bool ParsePaletteTypesArray(const ots::Font* font, 33 const uint8_t* data, size_t length, 34 std::vector<uint32_t>* paletteTypes) 35 { 36 ots::Buffer subtable(data, length); 37 38 constexpr uint32_t USABLE_WITH_LIGHT_BACKGROUND = 0x0001; 39 constexpr uint32_t USABLE_WITH_DARK_BACKGROUND = 0x0002; 40 constexpr uint32_t RESERVED = ~(USABLE_WITH_LIGHT_BACKGROUND | USABLE_WITH_DARK_BACKGROUND); 41 42 for (auto& type : *paletteTypes) { 43 if (!subtable.ReadU32(&type)) { 44 return OTS_FAILURE_MSG("Failed to read palette type"); 45 } 46 if (type & RESERVED) { 47 // Should we treat this as failure? For now, just a warning; seems unlikely 48 // to be dangerous. 49 OTS_WARNING("Invalid (reserved) palette type flags %08x", type); 50 type &= ~RESERVED; 51 } 52 } 53 54 return true; 55 } 56 57 // Caller has sized the labels array, so we know how much to try and read. 58 bool ParseLabelsArray(const ots::Font* font, 59 const uint8_t* data, size_t length, 60 std::vector<uint16_t>* labels, 61 const char* labelType) 62 { 63 ots::Buffer subtable(data, length); 64 65 auto* name = static_cast<ots::OpenTypeNAME*>(font->GetTypedTable(OTS_TAG_NAME)); 66 if (!name) { 67 return OTS_FAILURE_MSG("Required name table missing"); 68 } 69 70 for (auto& nameID : *labels) { 71 if (!subtable.ReadU16(&nameID)) { 72 return OTS_FAILURE_MSG("Failed to read %s label ID", labelType); 73 } 74 if (nameID != 0xffff) { 75 if (!name->IsValidNameId(nameID)) { 76 OTS_WARNING("Label ID %u for %s missing from name table", nameID, labelType); 77 nameID = 0xffff; 78 } 79 } 80 } 81 82 return true; 83 } 84 85 } // namespace 86 87 namespace ots { 88 89 bool OpenTypeCPAL::Parse(const uint8_t *data, size_t length) { 90 Font *font = GetFont(); 91 Buffer table(data, length); 92 93 // Header fields common to versions 0 and 1. These are recomputed 94 // from the array sizes during serialization. 95 uint16_t numPalettes; 96 uint16_t numColorRecords; 97 uint32_t colorRecordsArrayOffset; 98 99 if (!table.ReadU16(&this->version) || 100 !table.ReadU16(&this->num_palette_entries) || 101 !table.ReadU16(&numPalettes) || 102 !table.ReadU16(&numColorRecords) || 103 !table.ReadU32(&colorRecordsArrayOffset)) { 104 return Error("Failed to read CPAL table header"); 105 } 106 107 if (this->version > 1) { 108 return Error("Unknown CPAL table version %u", this->version); 109 } 110 111 if (!this->num_palette_entries || !numPalettes || !numColorRecords) { 112 return Error("Empty CPAL is not valid"); 113 } 114 115 if (this->num_palette_entries > numColorRecords) { 116 return Error("Not enough color records for a complete palette"); 117 } 118 119 uint32_t headerSize = 4 * sizeof(uint16_t) + sizeof(uint32_t) + 120 numPalettes * sizeof(uint16_t); 121 122 // uint16_t colorRecordIndices[numPalettes] 123 this->colorRecordIndices.resize(numPalettes); 124 for (auto& colorRecordIndex : this->colorRecordIndices) { 125 if (!table.ReadU16(&colorRecordIndex)) { 126 return Error("Failed to read color record index"); 127 } 128 if (colorRecordIndex > numColorRecords - this->num_palette_entries) { 129 return Error("Palette overflows color records array"); 130 } 131 } 132 133 uint32_t paletteTypesArrayOffset = 0; 134 uint32_t paletteLabelsArrayOffset = 0; 135 uint32_t paletteEntryLabelsArrayOffset = 0; 136 if (this->version == 1) { 137 if (!table.ReadU32(&paletteTypesArrayOffset) || 138 !table.ReadU32(&paletteLabelsArrayOffset) || 139 !table.ReadU32(&paletteEntryLabelsArrayOffset)) { 140 return Error("Failed to read CPAL v.1 table header"); 141 } 142 headerSize += 3 * sizeof(uint32_t); 143 } 144 145 // The following arrays may occur in any order, as they're independently referenced 146 // by offsets in the header. 147 148 if (colorRecordsArrayOffset < headerSize || colorRecordsArrayOffset >= length) { 149 return Error("Bad color records array offset in table header"); 150 } 151 this->colorRecords.resize(numColorRecords); 152 if (!ParseColorRecordsArray(font, data + colorRecordsArrayOffset, length - colorRecordsArrayOffset, 153 &this->colorRecords)) { 154 return Error("Failed to parse color records array"); 155 } 156 157 if (paletteTypesArrayOffset) { 158 if (paletteTypesArrayOffset < headerSize || paletteTypesArrayOffset >= length) { 159 return Error("Bad palette types array offset in table header"); 160 } 161 this->paletteTypes.resize(numPalettes); 162 if (!ParsePaletteTypesArray(font, data + paletteTypesArrayOffset, length - paletteTypesArrayOffset, 163 &this->paletteTypes)) { 164 return Error("Failed to parse palette types array"); 165 } 166 } 167 168 if (paletteLabelsArrayOffset) { 169 if (paletteLabelsArrayOffset < headerSize || paletteLabelsArrayOffset >= length) { 170 return Error("Bad palette labels array offset in table header"); 171 } 172 this->paletteLabels.resize(numPalettes); 173 if (!ParseLabelsArray(font, data + paletteLabelsArrayOffset, length - paletteLabelsArrayOffset, 174 &this->paletteLabels, "palette")) { 175 return Error("Failed to parse palette labels array"); 176 } 177 } 178 179 if (paletteEntryLabelsArrayOffset) { 180 if (paletteEntryLabelsArrayOffset < headerSize || paletteEntryLabelsArrayOffset >= length) { 181 return Error("Bad palette entry labels array offset in table header"); 182 } 183 this->paletteEntryLabels.resize(this->num_palette_entries); 184 if (!ParseLabelsArray(font, data + paletteEntryLabelsArrayOffset, length - paletteEntryLabelsArrayOffset, 185 &this->paletteEntryLabels, "palette entry")) { 186 return Error("Failed to parse palette entry labels array"); 187 } 188 } 189 190 return true; 191 } 192 193 bool OpenTypeCPAL::Serialize(OTSStream *out) { 194 uint16_t numPalettes = this->colorRecordIndices.size(); 195 uint16_t numColorRecords = this->colorRecords.size(); 196 197 #ifndef NDEBUG 198 off_t start = out->Tell(); 199 #endif 200 201 size_t colorRecordsArrayOffset = 4 * sizeof(uint16_t) + sizeof(uint32_t) + 202 numPalettes * sizeof(uint16_t); 203 if (this->version == 1) { 204 colorRecordsArrayOffset += 3 * sizeof(uint32_t); 205 } 206 207 size_t totalLen = colorRecordsArrayOffset + numColorRecords * sizeof(uint32_t); 208 209 if (!out->WriteU16(this->version) || 210 !out->WriteU16(this->num_palette_entries) || 211 !out->WriteU16(numPalettes) || 212 !out->WriteU16(numColorRecords) || 213 !out->WriteU32(colorRecordsArrayOffset)) { 214 return Error("Failed to write CPAL header"); 215 } 216 217 for (auto i : this->colorRecordIndices) { 218 if (!out->WriteU16(i)) { 219 return Error("Failed to write color record indices"); 220 } 221 } 222 223 if (this->version == 1) { 224 size_t paletteTypesArrayOffset = 0; 225 if (!this->paletteTypes.empty()) { 226 assert(paletteTypes.size() == numPalettes); 227 paletteTypesArrayOffset = totalLen; 228 totalLen += numPalettes * sizeof(uint32_t); 229 } 230 231 size_t paletteLabelsArrayOffset = 0; 232 if (!this->paletteLabels.empty()) { 233 assert(paletteLabels.size() == numPalettes); 234 paletteLabelsArrayOffset = totalLen; 235 totalLen += numPalettes * sizeof(uint16_t); 236 } 237 238 size_t paletteEntryLabelsArrayOffset = 0; 239 if (!this->paletteEntryLabels.empty()) { 240 assert(paletteEntryLabels.size() == this->num_palette_entries); 241 paletteEntryLabelsArrayOffset = totalLen; 242 totalLen += this->num_palette_entries * sizeof(uint16_t); 243 } 244 245 if (!out->WriteU32(paletteTypesArrayOffset) || 246 !out->WriteU32(paletteLabelsArrayOffset) || 247 !out->WriteU32(paletteEntryLabelsArrayOffset)) { 248 return Error("Failed to write CPAL v.1 header"); 249 } 250 } 251 252 for (auto i : this->colorRecords) { 253 if (!out->WriteU32(i)) { 254 return Error("Failed to write color records"); 255 } 256 } 257 258 if (this->version == 1) { 259 for (auto i : this->paletteTypes) { 260 if (!out->WriteU32(i)) { 261 return Error("Failed to write palette types"); 262 } 263 } 264 265 for (auto i : this->paletteLabels) { 266 if (!out->WriteU16(i)) { 267 return Error("Failed to write palette labels"); 268 } 269 } 270 271 for (auto i : this->paletteEntryLabels) { 272 if (!out->WriteU16(i)) { 273 return Error("Failed to write palette entry labels"); 274 } 275 } 276 } 277 278 assert(size_t(out->Tell() - start) == totalLen); 279 280 return true; 281 } 282 283 } // namespace ots 284 285 #undef TABLE_NAME