tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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