woff2_enc.cc (15623B)
1 /* Copyright 2014 Google Inc. All Rights Reserved. 2 3 Distributed under MIT license. 4 See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 */ 6 7 /* Library for converting TTF format font files to their WOFF2 versions. */ 8 9 #include <woff2/encode.h> 10 11 #include <stdlib.h> 12 #include <complex> 13 #include <cstring> 14 #include <limits> 15 #include <string> 16 #include <vector> 17 18 #include <brotli/encode.h> 19 #include "./buffer.h" 20 #include "./font.h" 21 #include "./normalize.h" 22 #include "./round.h" 23 #include "./store_bytes.h" 24 #include "./table_tags.h" 25 #include "./transform.h" 26 #include "./variable_length.h" 27 #include "./woff2_common.h" 28 29 namespace woff2 { 30 31 32 namespace { 33 34 const size_t kWoff2HeaderSize = 48; 35 const size_t kWoff2EntrySize = 20; 36 37 bool Compress(const uint8_t* data, const size_t len, uint8_t* result, 38 uint32_t* result_len, BrotliEncoderMode mode, int quality) { 39 size_t compressed_len = *result_len; 40 if (BrotliEncoderCompress(quality, BROTLI_DEFAULT_WINDOW, mode, len, data, 41 &compressed_len, result) == 0) { 42 return false; 43 } 44 *result_len = compressed_len; 45 return true; 46 } 47 48 bool Woff2Compress(const uint8_t* data, const size_t len, 49 uint8_t* result, uint32_t* result_len, 50 int quality) { 51 return Compress(data, len, result, result_len, 52 BROTLI_MODE_FONT, quality); 53 } 54 55 bool TextCompress(const uint8_t* data, const size_t len, 56 uint8_t* result, uint32_t* result_len, 57 int quality) { 58 return Compress(data, len, result, result_len, 59 BROTLI_MODE_TEXT, quality); 60 } 61 62 int KnownTableIndex(uint32_t tag) { 63 for (int i = 0; i < 63; ++i) { 64 if (tag == kKnownTags[i]) return i; 65 } 66 return 63; 67 } 68 69 void StoreTableEntry(const Table& table, size_t* offset, uint8_t* dst) { 70 uint8_t flag_byte = (table.flags & 0xC0) | KnownTableIndex(table.tag); 71 dst[(*offset)++] = flag_byte; 72 // The index here is treated as a set of flag bytes because 73 // bits 6 and 7 of the byte are reserved for future use as flags. 74 // 0x3f or 63 means an arbitrary table tag. 75 if ((flag_byte & 0x3f) == 0x3f) { 76 StoreU32(table.tag, offset, dst); 77 } 78 StoreBase128(table.src_length, offset, dst); 79 if ((table.flags & kWoff2FlagsTransform) != 0) { 80 StoreBase128(table.transform_length, offset, dst); 81 } 82 } 83 84 size_t TableEntrySize(const Table& table) { 85 uint8_t flag_byte = KnownTableIndex(table.tag); 86 size_t size = ((flag_byte & 0x3f) != 0x3f) ? 1 : 5; 87 size += Base128Size(table.src_length); 88 if ((table.flags & kWoff2FlagsTransform) != 0) { 89 size += Base128Size(table.transform_length); 90 } 91 return size; 92 } 93 94 size_t ComputeWoff2Length(const FontCollection& font_collection, 95 const std::vector<Table>& tables, 96 std::map<std::pair<uint32_t, uint32_t>, uint16_t> 97 index_by_tag_offset, 98 size_t compressed_data_length, 99 size_t extended_metadata_length) { 100 size_t size = kWoff2HeaderSize; 101 102 for (const auto& table : tables) { 103 size += TableEntrySize(table); 104 } 105 106 // for collections only, collection tables 107 if (font_collection.flavor == kTtcFontFlavor) { 108 size += 4; // UInt32 Version of TTC Header 109 size += Size255UShort(font_collection.fonts.size()); // 255UInt16 numFonts 110 111 size += 4 * font_collection.fonts.size(); // UInt32 flavor for each 112 113 for (const auto& font : font_collection.fonts) { 114 size += Size255UShort(font.tables.size()); // 255UInt16 numTables 115 for (const auto& entry : font.tables) { 116 const Font::Table& table = entry.second; 117 // no collection entry for xform table 118 if (table.tag & 0x80808080) continue; 119 120 std::pair<uint32_t, uint32_t> tag_offset(table.tag, table.offset); 121 uint16_t table_index = index_by_tag_offset[tag_offset]; 122 size += Size255UShort(table_index); // 255UInt16 index entry 123 } 124 } 125 } 126 127 // compressed data 128 size += compressed_data_length; 129 size = Round4(size); 130 131 size += extended_metadata_length; 132 return size; 133 } 134 135 size_t ComputeUncompressedLength(const Font& font) { 136 // sfnt header + offset table 137 size_t size = 12 + 16 * font.num_tables; 138 for (const auto& entry : font.tables) { 139 const Font::Table& table = entry.second; 140 if (table.tag & 0x80808080) continue; // xform tables don't stay 141 if (table.IsReused()) continue; // don't have to pay twice 142 size += Round4(table.length); 143 } 144 return size; 145 } 146 147 size_t ComputeUncompressedLength(const FontCollection& font_collection) { 148 if (font_collection.flavor != kTtcFontFlavor) { 149 return ComputeUncompressedLength(font_collection.fonts[0]); 150 } 151 size_t size = CollectionHeaderSize(font_collection.header_version, 152 font_collection.fonts.size()); 153 for (const auto& font : font_collection.fonts) { 154 size += ComputeUncompressedLength(font); 155 } 156 return size; 157 } 158 159 size_t ComputeTotalTransformLength(const Font& font) { 160 size_t total = 0; 161 for (const auto& i : font.tables) { 162 const Font::Table& table = i.second; 163 if (table.IsReused()) { 164 continue; 165 } 166 if (table.tag & 0x80808080 || !font.FindTable(table.tag ^ 0x80808080)) { 167 // Count transformed tables and non-transformed tables that do not have 168 // transformed versions. 169 total += table.length; 170 } 171 } 172 return total; 173 } 174 175 } // namespace 176 177 size_t MaxWOFF2CompressedSize(const uint8_t* data, size_t length) { 178 return MaxWOFF2CompressedSize(data, length, ""); 179 } 180 181 size_t MaxWOFF2CompressedSize(const uint8_t* data, size_t length, 182 const std::string& extended_metadata) { 183 // Except for the header size, which is 32 bytes larger in woff2 format, 184 // all other parts should be smaller (table header in short format, 185 // transformations and compression). Just to be sure, we will give some 186 // headroom anyway. 187 return length + 1024 + extended_metadata.length(); 188 } 189 190 uint32_t CompressedBufferSize(uint32_t original_size) { 191 return 1.2 * original_size + 10240; 192 } 193 194 bool TransformFontCollection(FontCollection* font_collection) { 195 for (auto& font : font_collection->fonts) { 196 if (!TransformGlyfAndLocaTables(&font)) { 197 #ifdef FONT_COMPRESSION_BIN 198 fprintf(stderr, "glyf/loca transformation failed.\n"); 199 #endif 200 return FONT_COMPRESSION_FAILURE(); 201 } 202 } 203 204 return true; 205 } 206 207 bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, 208 uint8_t *result, size_t *result_length) { 209 WOFF2Params params; 210 return ConvertTTFToWOFF2(data, length, result, result_length, 211 params); 212 } 213 214 bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, 215 uint8_t *result, size_t *result_length, 216 const WOFF2Params& params) { 217 FontCollection font_collection; 218 if (!ReadFontCollection(data, length, &font_collection)) { 219 #ifdef FONT_COMPRESSION_BIN 220 fprintf(stderr, "Parsing of the input font failed.\n"); 221 #endif 222 return FONT_COMPRESSION_FAILURE(); 223 } 224 225 if (!NormalizeFontCollection(&font_collection)) { 226 return FONT_COMPRESSION_FAILURE(); 227 } 228 229 if (params.allow_transforms && !TransformFontCollection(&font_collection)) { 230 return FONT_COMPRESSION_FAILURE(); 231 } else { 232 // glyf/loca use 11 to flag "not transformed" 233 for (auto& font : font_collection.fonts) { 234 Font::Table* glyf_table = font.FindTable(kGlyfTableTag); 235 Font::Table* loca_table = font.FindTable(kLocaTableTag); 236 if (glyf_table) { 237 glyf_table->flag_byte |= 0xc0; 238 } 239 if (loca_table) { 240 loca_table->flag_byte |= 0xc0; 241 } 242 } 243 } 244 245 // Although the compressed size of each table in the final woff2 file won't 246 // be larger than its transform_length, we have to allocate a large enough 247 // buffer for the compressor, since the compressor can potentially increase 248 // the size. If the compressor overflows this, it should return false and 249 // then this function will also return false. 250 251 size_t total_transform_length = 0; 252 for (const auto& font : font_collection.fonts) { 253 total_transform_length += ComputeTotalTransformLength(font); 254 } 255 size_t compression_buffer_size = CompressedBufferSize(total_transform_length); 256 std::vector<uint8_t> compression_buf(compression_buffer_size); 257 uint32_t total_compressed_length = compression_buffer_size; 258 259 // Collect all transformed data into one place in output order. 260 std::vector<uint8_t> transform_buf(total_transform_length); 261 size_t transform_offset = 0; 262 for (const auto& font : font_collection.fonts) { 263 for (const auto tag : font.OutputOrderedTags()) { 264 const Font::Table& original = font.tables.at(tag); 265 if (original.IsReused()) continue; 266 if (tag & 0x80808080) continue; 267 const Font::Table* table_to_store = font.FindTable(tag ^ 0x80808080); 268 if (table_to_store == NULL) table_to_store = &original; 269 270 StoreBytes(table_to_store->data, table_to_store->length, 271 &transform_offset, &transform_buf[0]); 272 } 273 } 274 275 // Compress all transformed data in one stream. 276 if (!Woff2Compress(transform_buf.data(), total_transform_length, 277 &compression_buf[0], 278 &total_compressed_length, 279 params.brotli_quality)) { 280 #ifdef FONT_COMPRESSION_BIN 281 fprintf(stderr, "Compression of combined table failed.\n"); 282 #endif 283 return FONT_COMPRESSION_FAILURE(); 284 } 285 286 #ifdef FONT_COMPRESSION_BIN 287 fprintf(stderr, "Compressed %zu to %u.\n", total_transform_length, 288 total_compressed_length); 289 #endif 290 291 // Compress the extended metadata 292 // TODO(user): how does this apply to collections 293 uint32_t compressed_metadata_buf_length = 294 CompressedBufferSize(params.extended_metadata.length()); 295 std::vector<uint8_t> compressed_metadata_buf(compressed_metadata_buf_length); 296 297 if (params.extended_metadata.length() > 0) { 298 if (!TextCompress((const uint8_t*)params.extended_metadata.data(), 299 params.extended_metadata.length(), 300 compressed_metadata_buf.data(), 301 &compressed_metadata_buf_length, 302 params.brotli_quality)) { 303 #ifdef FONT_COMPRESSION_BIN 304 fprintf(stderr, "Compression of extended metadata failed.\n"); 305 #endif 306 return FONT_COMPRESSION_FAILURE(); 307 } 308 } else { 309 compressed_metadata_buf_length = 0; 310 } 311 312 std::vector<Table> tables; 313 std::map<std::pair<uint32_t, uint32_t>, uint16_t> index_by_tag_offset; 314 315 for (const auto& font : font_collection.fonts) { 316 317 for (const auto tag : font.OutputOrderedTags()) { 318 const Font::Table& src_table = font.tables.at(tag); 319 if (src_table.IsReused()) { 320 continue; 321 } 322 323 std::pair<uint32_t, uint32_t> tag_offset(src_table.tag, src_table.offset); 324 if (index_by_tag_offset.find(tag_offset) == index_by_tag_offset.end()) { 325 index_by_tag_offset[tag_offset] = tables.size(); 326 } else { 327 return false; 328 } 329 330 Table table; 331 table.tag = src_table.tag; 332 table.flags = src_table.flag_byte; 333 table.src_length = src_table.length; 334 table.transform_length = src_table.length; 335 const uint8_t* transformed_data = src_table.data; 336 const Font::Table* transformed_table = 337 font.FindTable(src_table.tag ^ 0x80808080); 338 if (transformed_table != NULL) { 339 table.flags = transformed_table->flag_byte; 340 table.flags |= kWoff2FlagsTransform; 341 table.transform_length = transformed_table->length; 342 transformed_data = transformed_table->data; 343 344 } 345 tables.push_back(table); 346 } 347 } 348 349 size_t woff2_length = ComputeWoff2Length(font_collection, tables, 350 index_by_tag_offset, total_compressed_length, 351 compressed_metadata_buf_length); 352 if (woff2_length > *result_length) { 353 #ifdef FONT_COMPRESSION_BIN 354 fprintf(stderr, "Result allocation was too small (%zd vs %zd bytes).\n", 355 *result_length, woff2_length); 356 #endif 357 return FONT_COMPRESSION_FAILURE(); 358 } 359 *result_length = woff2_length; 360 361 size_t offset = 0; 362 363 // start of woff2 header (http://www.w3.org/TR/WOFF2/#woff20Header) 364 StoreU32(kWoff2Signature, &offset, result); 365 if (font_collection.flavor != kTtcFontFlavor) { 366 StoreU32(font_collection.fonts[0].flavor, &offset, result); 367 } else { 368 StoreU32(kTtcFontFlavor, &offset, result); 369 } 370 StoreU32(woff2_length, &offset, result); 371 Store16(tables.size(), &offset, result); 372 Store16(0, &offset, result); // reserved 373 // totalSfntSize 374 StoreU32(ComputeUncompressedLength(font_collection), &offset, result); 375 StoreU32(total_compressed_length, &offset, result); // totalCompressedSize 376 377 // Let's just all be v1.0 378 Store16(1, &offset, result); // majorVersion 379 Store16(0, &offset, result); // minorVersion 380 if (compressed_metadata_buf_length > 0) { 381 StoreU32(woff2_length - compressed_metadata_buf_length, 382 &offset, result); // metaOffset 383 StoreU32(compressed_metadata_buf_length, &offset, result); // metaLength 384 StoreU32(params.extended_metadata.length(), 385 &offset, result); // metaOrigLength 386 } else { 387 StoreU32(0, &offset, result); // metaOffset 388 StoreU32(0, &offset, result); // metaLength 389 StoreU32(0, &offset, result); // metaOrigLength 390 } 391 StoreU32(0, &offset, result); // privOffset 392 StoreU32(0, &offset, result); // privLength 393 // end of woff2 header 394 395 // table directory (http://www.w3.org/TR/WOFF2/#table_dir_format) 396 for (const auto& table : tables) { 397 StoreTableEntry(table, &offset, result); 398 } 399 400 // for collections only, collection table directory 401 if (font_collection.flavor == kTtcFontFlavor) { 402 StoreU32(font_collection.header_version, &offset, result); 403 Store255UShort(font_collection.fonts.size(), &offset, result); 404 for (const Font& font : font_collection.fonts) { 405 406 uint16_t num_tables = 0; 407 for (const auto& entry : font.tables) { 408 const Font::Table& table = entry.second; 409 if (table.tag & 0x80808080) continue; // don't write xform tables 410 num_tables++; 411 } 412 Store255UShort(num_tables, &offset, result); 413 414 StoreU32(font.flavor, &offset, result); 415 for (const auto& entry : font.tables) { 416 const Font::Table& table = entry.second; 417 if (table.tag & 0x80808080) continue; // don't write xform tables 418 419 // for reused tables, only the original has an updated offset 420 uint32_t table_offset = 421 table.IsReused() ? table.reuse_of->offset : table.offset; 422 uint32_t table_length = 423 table.IsReused() ? table.reuse_of->length : table.length; 424 std::pair<uint32_t, uint32_t> tag_offset(table.tag, table_offset); 425 if (index_by_tag_offset.find(tag_offset) == index_by_tag_offset.end()) { 426 #ifdef FONT_COMPRESSION_BIN 427 fprintf(stderr, "Missing table index for offset 0x%08x\n", 428 table_offset); 429 #endif 430 return FONT_COMPRESSION_FAILURE(); 431 } 432 uint16_t index = index_by_tag_offset[tag_offset]; 433 Store255UShort(index, &offset, result); 434 435 } 436 437 } 438 } 439 440 // compressed data format (http://www.w3.org/TR/WOFF2/#table_format) 441 442 StoreBytes(&compression_buf[0], total_compressed_length, &offset, result); 443 offset = Round4(offset); 444 445 StoreBytes(compressed_metadata_buf.data(), compressed_metadata_buf_length, 446 &offset, result); 447 448 if (*result_length != offset) { 449 #ifdef FONT_COMPRESSION_BIN 450 fprintf(stderr, "Mismatch between computed and actual length " 451 "(%zd vs %zd)\n", *result_length, offset); 452 #endif 453 return FONT_COMPRESSION_FAILURE(); 454 } 455 return true; 456 } 457 458 } // namespace woff2