diff --git a/Makefile b/Makefile index fcb9525..0048b77 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ endif SRCDIR = src OUROBJ = font.o glyph.o normalize.o table_tags.o transform.o \ - woff2_dec.o woff2_enc.o + woff2_dec.o woff2_enc.o woff2_common.o variable_length.o BROTLI = brotli ENCOBJ = $(BROTLI)/enc/*.o diff --git a/src/font.cc b/src/font.cc index a7e5607..babd9c0 100644 --- a/src/font.cc +++ b/src/font.cc @@ -22,6 +22,7 @@ #include "./port.h" #include "./store_bytes.h" #include "./table_tags.h" +#include "./woff2_common.h" namespace woff2 { @@ -35,24 +36,51 @@ const Font::Table* Font::FindTable(uint32_t tag) const { return it == tables.end() ? 0 : &it->second; } -bool ReadFont(const uint8_t* data, size_t len, Font* font) { - Buffer file(data, len); +std::vector Font::OutputOrderedTags() const { + std::vector output_order; + for (const auto& i : tables) { + const Font::Table& table = i.second; + // This is a transformed table, we will write it together with the + // original version. + if (table.tag & 0x80808080) { + continue; + } + output_order.push_back(table.tag); + } + + // alphabetize (not required), then put loca immediately after glyf (required) + std::sort(output_order.begin(), output_order.end()); + auto glyf_loc = std::find(output_order.begin(), output_order.end(), + kGlyfTableTag); + auto loca_loc = std::find(output_order.begin(), output_order.end(), + kLocaTableTag); + if (glyf_loc != output_order.end() && loca_loc != output_order.end()) { + output_order.erase(loca_loc); + output_order.insert(std::find(output_order.begin(), output_order.end(), + kGlyfTableTag) + 1, kLocaTableTag); + } + + return output_order; +} + +bool ReadTrueTypeFont(Buffer* file, const uint8_t* data, size_t len, + Font* font) { // We don't care about the search_range, entry_selector and range_shift // fields, they will always be computed upon writing the font. - if (!file.ReadU32(&font->flavor) || - !file.ReadU16(&font->num_tables) || - !file.Skip(6)) { + if (!file->ReadU16(&font->num_tables) || + !file->Skip(6)) { return FONT_COMPRESSION_FAILURE(); } std::map intervals; for (uint16_t i = 0; i < font->num_tables; ++i) { Font::Table table; - if (!file.ReadU32(&table.tag) || - !file.ReadU32(&table.checksum) || - !file.ReadU32(&table.offset) || - !file.ReadU32(&table.length)) { + table.reuse_of = NULL; + if (!file->ReadU32(&table.tag) || + !file->ReadU32(&table.checksum) || + !file->ReadU32(&table.offset) || + !file->ReadU32(&table.length)) { return FONT_COMPRESSION_FAILURE(); } if ((table.offset & 3) != 0 || @@ -76,9 +104,97 @@ bool ReadFont(const uint8_t* data, size_t len, Font* font) { } last_offset = i.first + i.second; } + return true; } +bool ReadCollectionFont(Buffer* file, const uint8_t* data, size_t len, + Font* font, + std::map* all_tables) { + if (!file->ReadU32(&font->flavor)) { + return FONT_COMPRESSION_FAILURE(); + } + if (!ReadTrueTypeFont(file, data, len, font)) { + return FONT_COMPRESSION_FAILURE(); + } + + for (auto& entry : font->tables) { + Font::Table& table = entry.second; + + if (all_tables->find(table.offset) == all_tables->end()) { + (*all_tables)[table.offset] = font->FindTable(table.tag); + } else { + table.reuse_of = (*all_tables)[table.offset]; + } + + } + return true; +} + +bool ReadTrueTypeCollection(Buffer* file, const uint8_t* data, size_t len, + FontCollection* font_collection) { + uint32_t num_fonts; + + if (!file->ReadU32(&font_collection->header_version) || + !file->ReadU32(&num_fonts)) { + return FONT_COMPRESSION_FAILURE(); + } + + std::vector offsets; + for (auto i = 0; i < num_fonts; i++) { + uint32_t offset; + if (!file->ReadU32(&offset)) { + return FONT_COMPRESSION_FAILURE(); + } + offsets.push_back(offset); + } + + font_collection->fonts.resize(offsets.size()); + std::vector::iterator font_it = font_collection->fonts.begin(); + + std::map all_tables; + for (const auto offset : offsets) { + file->set_offset(offset); + Font& font = *font_it++; + if (!ReadCollectionFont(file, data, len, &font, &all_tables)) { + return FONT_COMPRESSION_FAILURE(); + } + } + + return true; +} + +bool ReadFont(const uint8_t* data, size_t len, Font* font) { + Buffer file(data, len); + + if (!file.ReadU32(&font->flavor)) { + return FONT_COMPRESSION_FAILURE(); + } + + if (font->flavor == kTtcFontFlavor) { + return FONT_COMPRESSION_FAILURE(); + } + return ReadTrueTypeFont(&file, data, len, font); +} + +bool ReadFontCollection(const uint8_t* data, size_t len, + FontCollection* font_collection) { + Buffer file(data, len); + + uint32_t flavor; + if (!file.ReadU32(&flavor)) { + return FONT_COMPRESSION_FAILURE(); + } + + if (flavor != kTtcFontFlavor) { + font_collection->fonts.resize(1); + Font& font = font_collection->fonts[0]; + font.flavor = flavor; + return ReadTrueTypeFont(&file, data, len, &font); + } + return ReadTrueTypeCollection(&file, data, len, font_collection); +} + size_t FontFileSize(const Font& font) { size_t max_offset = 12ULL + 16ULL * font.num_tables; for (const auto& i : font.tables) { @@ -90,29 +206,34 @@ size_t FontFileSize(const Font& font) { return max_offset; } +size_t FontCollectionFileSize(const FontCollection& font_collection) { + size_t max_offset = 0; + for (auto& font : font_collection.fonts) { + // font file size actually just finds max offset + max_offset = std::max(max_offset, FontFileSize(font)); + } + return max_offset; +} + bool WriteFont(const Font& font, uint8_t* dst, size_t dst_size) { - if (dst_size < 12ULL + 16ULL * font.num_tables) { + size_t offset = 0; + return WriteFont(font, &offset, dst, dst_size, true); +} + +bool WriteTable(const Font::Table& table, size_t* offset, uint8_t* dst, + size_t dst_size) { + StoreU32(table.tag, offset, dst); + StoreU32(table.checksum, offset, dst); + StoreU32(table.offset, offset, dst); + StoreU32(table.length, offset, dst); + + if (table.offset + table.length < table.offset || + dst_size < table.offset + table.length) { return FONT_COMPRESSION_FAILURE(); } - size_t offset = 0; - StoreU32(font.flavor, &offset, dst); - Store16(font.num_tables, &offset, dst); - uint16_t max_pow2 = font.num_tables ? Log2Floor(font.num_tables) : 0; - uint16_t search_range = max_pow2 ? 1 << (max_pow2 + 4) : 0; - uint16_t range_shift = (font.num_tables << 4) - search_range; - Store16(search_range, &offset, dst); - Store16(max_pow2, &offset, dst); - Store16(range_shift, &offset, dst); - for (const auto& i : font.tables) { - const Font::Table& table = i.second; - StoreU32(table.tag, &offset, dst); - StoreU32(table.checksum, &offset, dst); - StoreU32(table.offset, &offset, dst); - StoreU32(table.length, &offset, dst); - if (table.offset + table.length < table.offset || - dst_size < table.offset + table.length) { - return FONT_COMPRESSION_FAILURE(); - } + + // Write the actual table data if it's the first time we've seen it + if (!table.IsReused()) { memcpy(dst + table.offset, table.data, table.length); size_t padding_size = (4 - (table.length & 3)) & 3; if (table.offset + table.length + padding_size < padding_size || @@ -124,6 +245,73 @@ bool WriteFont(const Font& font, uint8_t* dst, size_t dst_size) { return true; } +bool WriteFont(const Font& font, size_t* offset, uint8_t* dst, + size_t dst_size, bool reorder_tables) { + if (dst_size < 12ULL + 16ULL * font.num_tables) { + return FONT_COMPRESSION_FAILURE(); + } + StoreU32(font.flavor, offset, dst); + Store16(font.num_tables, offset, dst); + uint16_t max_pow2 = font.num_tables ? Log2Floor(font.num_tables) : 0; + uint16_t search_range = max_pow2 ? 1 << (max_pow2 + 4) : 0; + uint16_t range_shift = (font.num_tables << 4) - search_range; + Store16(search_range, offset, dst); + Store16(max_pow2, offset, dst); + Store16(range_shift, offset, dst); + if (reorder_tables) { + for (const auto tag : font.OutputOrderedTags()) { + if (!WriteTable(font.tables.at(tag), offset, dst, dst_size)) { + return false; + } + } + } else { + for (const auto& i : font.tables) { + if (!WriteTable(i.second, offset, dst, dst_size)) { + return false; + } + } + } + return true; +} + +bool WriteFontCollection(const FontCollection& font_collection, uint8_t* dst, + size_t dst_size) { + size_t offset = 0; + + // It's simpler if this just a simple sfnt + if (font_collection.fonts.size() == 1) { + return WriteFont(font_collection.fonts[0], &offset, dst, dst_size, true); + } + + // Write TTC header + StoreU32(kTtcFontFlavor, &offset, dst); + StoreU32(font_collection.header_version, &offset, dst); + StoreU32(font_collection.fonts.size(), &offset, dst); + + // Offset Table, zeroed for now + size_t offset_table = offset; // where to write offsets later + for (int i = 0; i < font_collection.fonts.size(); i++) { + StoreU32(0, &offset, dst); + } + + if (font_collection.header_version == 0x00020000) { + StoreU32(0, &offset, dst); // ulDsigTag + StoreU32(0, &offset, dst); // ulDsigLength + StoreU32(0, &offset, dst); // ulDsigOffset + } + + // Write fonts and their offsets. + for (int i = 0; i < font_collection.fonts.size(); i++) { + const auto& font = font_collection.fonts[i]; + StoreU32(offset, &offset_table, dst); + if (!WriteFont(font, &offset, dst, dst_size, false)) { + return false; + } + } + + return true; +} + int NumGlyphs(const Font& font) { const Font::Table* head_table = font.FindTable(kHeadTableTag); const Font::Table* loca_table = font.FindTable(kLocaTableTag); @@ -143,6 +331,10 @@ int IndexFormat(const Font& font) { return head_table->data[51]; } +bool Font::Table::IsReused() const { + return this->reuse_of != NULL; +} + bool GetGlyphData(const Font& font, int glyph_index, const uint8_t** glyph_data, size_t* glyph_size) { if (glyph_index < 0) { diff --git a/src/font.h b/src/font.h index 08c414c..95afe0c 100644 --- a/src/font.h +++ b/src/font.h @@ -41,25 +41,54 @@ struct Font { // Buffer used to mutate the data before writing out. std::vector buffer; + + // If we've seen this tag/offset before, pointer to the first time we saw it + // If this is the first time we've seen this table, NULL + // Intended use is to bypass re-processing tables + Font::Table* reuse_of; + + // Is this table reused by a TTC + bool IsReused() const; }; std::map tables; + std::vector OutputOrderedTags() const; Table* FindTable(uint32_t tag); const Table* FindTable(uint32_t tag) const; }; +// Accomodates both singular (OTF, TTF) and collection (TTC) fonts +struct FontCollection { + uint32_t header_version; + // (offset, first use of table*) pairs + std::map tables; + std::vector fonts; +}; + // Parses the font from the given data. Returns false on parsing failure or // buffer overflow. The font is valid only so long the input data pointer is -// valid. +// valid. Does NOT support collections. bool ReadFont(const uint8_t* data, size_t len, Font* font); +// Parses the font from the given data. Returns false on parsing failure or +// buffer overflow. The font is valid only so long the input data pointer is +// valid. Supports collections. +bool ReadFontCollection(const uint8_t* data, size_t len, FontCollection* fonts); + // Returns the file size of the font. size_t FontFileSize(const Font& font); +size_t FontCollectionFileSize(const FontCollection& font); // Writes the font into the specified dst buffer. The dst_size should be the // same as returned by FontFileSize(). Returns false upon buffer overflow (which // should not happen if dst_size was computed by FontFileSize()). bool WriteFont(const Font& font, uint8_t* dst, size_t dst_size); +// Write the font at a specific offset, optionally reordering tables +bool WriteFont(const Font& font, size_t* offset, uint8_t* dst, size_t dst_size, + bool reorder_tables); + +bool WriteFontCollection(const FontCollection& font_collection, uint8_t* dst, + size_t dst_size); // Returns the number of glyphs in the font. // NOTE: Currently this works only for TrueType-flavored fonts, will return diff --git a/src/normalize.cc b/src/normalize.cc index a816feb..36a4538 100644 --- a/src/normalize.cc +++ b/src/normalize.cc @@ -26,6 +26,7 @@ #include "./round.h" #include "./store_bytes.h" #include "./table_tags.h" +#include "./woff2_common.h" namespace woff2 { @@ -123,6 +124,9 @@ bool MakeEditableBuffer(Font* font, int tableTag) { if (table == NULL) { return FONT_COMPRESSION_FAILURE(); } + if (table->IsReused()) { + return true; + } int sz = Round4(table->length); table->buffer.resize(sz); uint8_t* buf = &table->buffer[0]; @@ -148,6 +152,15 @@ bool NormalizeGlyphs(Font* font) { if (loca_table == NULL || glyf_table == NULL) { return FONT_COMPRESSION_FAILURE(); } + + // Must share neither or both loca & glyf + if (loca_table->IsReused() != glyf_table->IsReused()) { + return FONT_COMPRESSION_FAILURE(); + } + if (loca_table->IsReused()) { + return true; + } + int index_fmt = head_table->data[51]; int num_glyphs = NumGlyphs(*font); @@ -184,26 +197,16 @@ bool NormalizeGlyphs(Font* font) { bool NormalizeOffsets(Font* font) { uint32_t offset = 12 + 16 * font->num_tables; - for (auto& i : font->tables) { - i.second.offset = offset; - offset += Round4(i.second.length); + for (auto tag : font->OutputOrderedTags()) { + auto& table = font->tables[tag]; + table.offset = offset; + offset += Round4(table.length); } return true; } namespace { -uint32_t ComputeChecksum(const uint8_t* buf, size_t size) { - uint32_t checksum = 0; - for (size_t i = 0; i < size; i += 4) { - checksum += ((buf[i] << 24) | - (buf[i + 1] << 16) | - (buf[i + 2] << 8) | - buf[i + 3]); - } - return checksum; -} - uint32_t ComputeHeaderChecksum(const Font& font) { uint32_t checksum = font.flavor; uint16_t max_pow2 = font.num_tables ? Log2Floor(font.num_tables) : 0; @@ -224,6 +227,9 @@ uint32_t ComputeHeaderChecksum(const Font& font) { bool FixChecksums(Font* font) { Font::Table* head_table = font->FindTable(kHeadTableTag); + if (head_table->reuse_of != NULL) { + head_table = head_table->reuse_of; + } if (head_table == NULL || head_table->length < 12) { return FONT_COMPRESSION_FAILURE(); } @@ -233,21 +239,68 @@ bool FixChecksums(Font* font) { uint32_t file_checksum = 0; for (auto& i : font->tables) { Font::Table* table = &i.second; - table->checksum = ComputeChecksum(table->data, table->length); + if (table->IsReused()) { + table = table->reuse_of; + } + table->checksum = ComputeULongSum(table->data, table->length); file_checksum += table->checksum; } + file_checksum += ComputeHeaderChecksum(*font); offset = 8; StoreU32(0xb1b0afba - file_checksum, &offset, head_buf); return true; } -bool NormalizeFont(Font* font) { +bool NormalizeWithoutFixingChecksums(Font* font) { return (MakeEditableBuffer(font, kHeadTableTag) && RemoveDigitalSignature(font) && NormalizeGlyphs(font) && - NormalizeOffsets(font) && + NormalizeOffsets(font)); +} + +bool NormalizeFont(Font* font) { + return (NormalizeWithoutFixingChecksums(font) && FixChecksums(font)); } +bool NormalizeFontCollection(FontCollection* font_collection) { + if (font_collection->fonts.size() == 1) { + return NormalizeFont(&font_collection->fonts[0]); + } + + uint32_t offset = CollectionHeaderSize(font_collection->header_version, + font_collection->fonts.size()); + for (auto& font : font_collection->fonts) { + if (!NormalizeWithoutFixingChecksums(&font)) { + fprintf(stderr, "Font normalization failed.\n"); + return false; + } + offset += kSfntHeaderSize + kSfntEntrySize * font.num_tables; + } + + // Start table offsets after TTC Header and Sfnt Headers + for (auto& font : font_collection->fonts) { + for (auto tag : font.OutputOrderedTags()) { + Font::Table& table = font.tables[tag]; + if (table.IsReused()) { + table.offset = table.reuse_of->offset; + } else { + table.offset = offset; + offset += Round4(table.length); + } + } + } + + // Now we can fix the checksums + for (auto& font : font_collection->fonts) { + if (!FixChecksums(&font)) { + fprintf(stderr, "Failed to fix checksums\n"); + return false; + } + } + + return true; +} + } // namespace woff2 diff --git a/src/normalize.h b/src/normalize.h index dcb473b..e015348 100644 --- a/src/normalize.h +++ b/src/normalize.h @@ -22,6 +22,7 @@ namespace woff2 { struct Font; +struct FontCollection; // Changes the offset fields of the table headers so that the data for the // tables will be written in order of increasing tag values, without any gaps @@ -39,6 +40,7 @@ bool NormalizeGlyphs(Font* font); // Performs all of the normalization steps above. bool NormalizeFont(Font* font); +bool NormalizeFontCollection(FontCollection* font_collection); } // namespace woff2 diff --git a/src/transform.cc b/src/transform.cc index 44a4781..9eb0d1c 100644 --- a/src/transform.cc +++ b/src/transform.cc @@ -22,6 +22,7 @@ #include "./font.h" #include "./glyph.h" #include "./table_tags.h" +#include "./variable_length.h" namespace woff2 { @@ -55,22 +56,6 @@ void WriteLong(std::vector* out, int value) { out->push_back(value & 255); } -void Write255UShort(std::vector* out, int value) { - if (value < 253) { - out->push_back(value); - } else if (value < 506) { - out->push_back(255); - out->push_back(value - 253); - } else if (value < 762) { - out->push_back(254); - out->push_back(value - 506); - } else { - out->push_back(253); - out->push_back(value >> 8); - out->push_back(value & 0xff); - } -} - // Glyf table preprocessing, based on // GlyfEncoder.java // but only the "sbbox" and "cbbox" options are supported. @@ -228,9 +213,18 @@ class GlyfEncoder { bool TransformGlyfAndLocaTables(Font* font) { // no transform for CFF + const Font::Table* glyf_table = font->FindTable(kGlyfTableTag); + const Font::Table* loca_table = font->FindTable(kLocaTableTag); if (font->FindTable(kCffTableTag) != NULL - && font->FindTable(kGlyfTableTag) == NULL - && font->FindTable(kLocaTableTag) == NULL) { + && glyf_table == NULL + && loca_table == NULL) { + return true; + } + // Must share neither or both loca/glyf + if (glyf_table->IsReused() != loca_table->IsReused()) { + return FONT_COMPRESSION_FAILURE(); + } + if (glyf_table->IsReused()) { return true; } Font::Table* transformed_glyf = &font->tables[kGlyfTableTag ^ 0x80808080]; diff --git a/src/variable_length.cc b/src/variable_length.cc new file mode 100644 index 0000000..9a85623 --- /dev/null +++ b/src/variable_length.cc @@ -0,0 +1,133 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Helper functions for woff2 variable length types: 255UInt16 and UIntBase128 + +#include "./variable_length.h" + +namespace woff2 { + +size_t Size255UShort(uint16_t value) { + size_t result = 3; + if (value < 253) { + result = 1; + } else if (value < 762) { + result = 2; + } else { + result = 3; + } + return result; +} + +void Write255UShort(std::vector* out, int value) { + if (value < 253) { + out->push_back(value); + } else if (value < 506) { + out->push_back(255); + out->push_back(value - 253); + } else if (value < 762) { + out->push_back(254); + out->push_back(value - 506); + } else { + out->push_back(253); + out->push_back(value >> 8); + out->push_back(value & 0xff); + } +} + +void Store255UShort(int val, size_t* offset, uint8_t* dst) { + std::vector packed; + Write255UShort(&packed, val); + for (uint8_t val : packed) { + dst[(*offset)++] = val; + } +} + +// Based on section 6.1.1 of MicroType Express draft spec +bool Read255UShort(Buffer* buf, unsigned int* value) { + static const int kWordCode = 253; + static const int kOneMoreByteCode2 = 254; + static const int kOneMoreByteCode1 = 255; + static const int kLowestUCode = 253; + uint8_t code = 0; + if (!buf->ReadU8(&code)) { + return FONT_COMPRESSION_FAILURE(); + } + if (code == kWordCode) { + uint16_t result = 0; + if (!buf->ReadU16(&result)) { + return FONT_COMPRESSION_FAILURE(); + } + *value = result; + return true; + } else if (code == kOneMoreByteCode1) { + uint8_t result = 0; + if (!buf->ReadU8(&result)) { + return FONT_COMPRESSION_FAILURE(); + } + *value = result + kLowestUCode; + return true; + } else if (code == kOneMoreByteCode2) { + uint8_t result = 0; + if (!buf->ReadU8(&result)) { + return FONT_COMPRESSION_FAILURE(); + } + *value = result + kLowestUCode * 2; + return true; + } else { + *value = code; + return true; + } +} + +bool ReadBase128(Buffer* buf, uint32_t* value) { + uint32_t result = 0; + for (size_t i = 0; i < 5; ++i) { + uint8_t code = 0; + if (!buf->ReadU8(&code)) { + return FONT_COMPRESSION_FAILURE(); + } + // If any of the top seven bits are set then we're about to overflow. + if (result & 0xfe000000) { + return FONT_COMPRESSION_FAILURE(); + } + result = (result << 7) | (code & 0x7f); + if ((code & 0x80) == 0) { + *value = result; + return true; + } + } + // Make sure not to exceed the size bound + return FONT_COMPRESSION_FAILURE(); +} + +size_t Base128Size(size_t n) { + size_t size = 1; + for (; n >= 128; n >>= 7) ++size; + return size; +} + +void StoreBase128(size_t len, size_t* offset, uint8_t* dst) { + size_t size = Base128Size(len); + for (int i = 0; i < size; ++i) { + int b = static_cast((len >> (7 * (size - i - 1))) & 0x7f); + if (i < size - 1) { + b |= 0x80; + } + dst[(*offset)++] = b; + } +} + +} // namespace woff2 + diff --git a/src/variable_length.h b/src/variable_length.h new file mode 100644 index 0000000..2816ae2 --- /dev/null +++ b/src/variable_length.h @@ -0,0 +1,38 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Helper functions for woff2 variable length types: 255UInt16 and UIntBase128 + +#ifndef WOFF2_VARIABLE_LENGTH_H_ +#define WOFF2_VARIABLE_LENGTH_H_ + +#include +#include +#include "./buffer.h" + +namespace woff2 { + +size_t Size255UShort(uint16_t value); +bool Read255UShort(Buffer* buf, unsigned int* value); +void Write255UShort(std::vector* out, int value); +void Store255UShort(int val, size_t* offset, uint8_t* dst); + +size_t Base128Size(size_t n); +bool ReadBase128(Buffer* buf, uint32_t* value); +void StoreBase128(size_t len, size_t* offset, uint8_t* dst); + +} // namespace woff2 + +#endif // WOFF2_VARIABLE_LENGTH_H_ + diff --git a/src/woff2_common.cc b/src/woff2_common.cc new file mode 100644 index 0000000..de5998b --- /dev/null +++ b/src/woff2_common.cc @@ -0,0 +1,46 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Helpers common across multiple parts of woff2 + +#include + +#include "./woff2_common.h" + +namespace woff2 { + + +uint32_t ComputeULongSum(const uint8_t* buf, size_t size) { + uint32_t checksum = 0; + for (size_t i = 0; i < size; i += 4) { + // We assume the addition is mod 2^32, which is valid because unsigned + checksum += (buf[i] << 24) | (buf[i + 1] << 16) | + (buf[i + 2] << 8) | buf[i + 3]; + } + return checksum; +} + +size_t CollectionHeaderSize(uint32_t header_version, uint32_t num_fonts) { + size_t size = 0; + if (header_version == 0x00020000) { + size += 12; // ulDsig{Tag,Length,Offset} + } + if (header_version == 0x00010000 || header_version == 0x00020000) { + size += 12 // TTCTag, Version, numFonts + + 4 * num_fonts; // OffsetTable[numFonts] + } + return size; +} + +} // namespace woff2 diff --git a/src/woff2_common.h b/src/woff2_common.h index 159300a..f1df21f 100644 --- a/src/woff2_common.h +++ b/src/woff2_common.h @@ -17,8 +17,11 @@ #ifndef WOFF2_WOFF2_COMMON_H_ #define WOFF2_WOFF2_COMMON_H_ +#include #include +#include + namespace woff2 { static const uint32_t kWoff2Signature = 0x774f4632; // "wOF2" @@ -26,6 +29,12 @@ static const uint32_t kWoff2Signature = 0x774f4632; // "wOF2" const unsigned int kWoff2FlagsContinueStream = 1 << 4; const unsigned int kWoff2FlagsTransform = 1 << 5; +// TrueType Collection ID string: 'ttcf' +static const uint32_t kTtcFontFlavor = 0x74746366; + +static const size_t kSfntHeaderSize = 12; +static const size_t kSfntEntrySize = 16; + struct Point { int x; int y; @@ -43,12 +52,17 @@ struct Table { uint32_t dst_offset; uint32_t dst_length; const uint8_t* dst_data; - - bool operator<(const Table& other) const { - return tag < other.tag; - } }; + +// Size of the collection header. 0 if version indicates this isn't a +// collection. Ref http://www.microsoft.com/typography/otspec/otff.htm, +// True Type Collections +size_t CollectionHeaderSize(uint32_t header_version, uint32_t num_fonts); + +// Compute checksum over size bytes of buf +uint32_t ComputeULongSum(const uint8_t* buf, size_t size); + } // namespace woff2 #endif // WOFF2_WOFF2_COMMON_H_ diff --git a/src/woff2_dec.cc b/src/woff2_dec.cc index e2bf6fb..b18339f 100644 --- a/src/woff2_dec.cc +++ b/src/woff2_dec.cc @@ -21,14 +21,15 @@ #include #include #include -#include #include +#include #include "./buffer.h" #include "./decode.h" #include "./round.h" #include "./store_bytes.h" #include "./table_tags.h" +#include "./variable_length.h" #include "./woff2_common.h" namespace woff2 { @@ -56,70 +57,17 @@ const int FLAG_WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6; const int FLAG_WE_HAVE_A_TWO_BY_TWO = 1 << 7; const int FLAG_WE_HAVE_INSTRUCTIONS = 1 << 8; -const size_t kSfntHeaderSize = 12; -const size_t kSfntEntrySize = 16; const size_t kCheckSumAdjustmentOffset = 8; const size_t kEndPtsOfContoursOffset = 10; const size_t kCompositeGlyphBegin = 10; -// Based on section 6.1.1 of MicroType Express draft spec -bool Read255UShort(Buffer* buf, unsigned int* value) { - static const int kWordCode = 253; - static const int kOneMoreByteCode2 = 254; - static const int kOneMoreByteCode1 = 255; - static const int kLowestUCode = 253; - uint8_t code = 0; - if (!buf->ReadU8(&code)) { - return FONT_COMPRESSION_FAILURE(); - } - if (code == kWordCode) { - uint16_t result = 0; - if (!buf->ReadU16(&result)) { - return FONT_COMPRESSION_FAILURE(); - } - *value = result; - return true; - } else if (code == kOneMoreByteCode1) { - uint8_t result = 0; - if (!buf->ReadU8(&result)) { - return FONT_COMPRESSION_FAILURE(); - } - *value = result + kLowestUCode; - return true; - } else if (code == kOneMoreByteCode2) { - uint8_t result = 0; - if (!buf->ReadU8(&result)) { - return FONT_COMPRESSION_FAILURE(); - } - *value = result + kLowestUCode * 2; - return true; - } else { - *value = code; - return true; - } -} - -bool ReadBase128(Buffer* buf, uint32_t* value) { - uint32_t result = 0; - for (size_t i = 0; i < 5; ++i) { - uint8_t code = 0; - if (!buf->ReadU8(&code)) { - return FONT_COMPRESSION_FAILURE(); - } - // If any of the top seven bits are set then we're about to overflow. - if (result & 0xfe000000) { - return FONT_COMPRESSION_FAILURE(); - } - result = (result << 7) | (code & 0x7f); - if ((code & 0x80) == 0) { - *value = result; - return true; - } - } - // Make sure not to exceed the size bound - return FONT_COMPRESSION_FAILURE(); -} +// metadata for a TTC font entry +struct TtcFont { + uint32_t flavor; + uint32_t dst_offset; + std::vector table_indices; +}; int WithSign(int flag, int baseval) { // Precondition: 0 <= baseval < 65536 (to avoid integer overflow) @@ -394,7 +342,7 @@ bool ProcessComposite(Buffer* composite_stream, uint8_t* dst, // Build TrueType loca table bool StoreLoca(const std::vector& loca_values, int index_format, - uint8_t* dst, size_t dst_size) { + uint8_t* dst, size_t dst_size) { const uint64_t loca_size = loca_values.size(); const uint64_t offset_size = index_format ? 4 : 2; if ((loca_size << 2) >> 2 != loca_size) { @@ -417,8 +365,8 @@ bool StoreLoca(const std::vector& loca_values, int index_format, // Reconstruct entire glyf table based on transformed original bool ReconstructGlyf(const uint8_t* data, size_t data_size, - uint8_t* dst, size_t dst_size, - uint8_t* loca_buf, size_t loca_size) { + uint8_t* dst, size_t dst_size, + uint8_t* loca_buf, size_t loca_size) { static const int kNumSubStreams = 7; Buffer file(data, data_size); uint32_t version; @@ -433,6 +381,7 @@ bool ReconstructGlyf(const uint8_t* data, size_t data_size, !file.ReadU16(&index_format)) { return FONT_COMPRESSION_FAILURE(); } + unsigned int offset = (2 + kNumSubStreams) * 4; if (offset > data_size) { return FONT_COMPRESSION_FAILURE(); @@ -595,26 +544,33 @@ const Table* FindTable(const std::vector& tables, uint32_t tag) { return NULL; } +bool ReconstructTransformedGlyf(const uint8_t* transformed_buf, + size_t transformed_size, const Table* glyf_table, const Table* loca_table, + uint8_t* dst, size_t dst_length) { + if (glyf_table == NULL || loca_table == NULL) { + return FONT_COMPRESSION_FAILURE(); + } + if (static_cast(glyf_table->dst_offset + glyf_table->dst_length) > + dst_length) { + return FONT_COMPRESSION_FAILURE(); + } + if (static_cast(loca_table->dst_offset + loca_table->dst_length) > + dst_length) { + return FONT_COMPRESSION_FAILURE(); + } + return ReconstructGlyf(transformed_buf, transformed_size, + dst + glyf_table->dst_offset, glyf_table->dst_length, + dst + loca_table->dst_offset, loca_table->dst_length); +} + bool ReconstructTransformed(const std::vector
& tables, uint32_t tag, const uint8_t* transformed_buf, size_t transformed_size, uint8_t* dst, size_t dst_length) { if (tag == kGlyfTableTag) { const Table* glyf_table = FindTable(tables, tag); const Table* loca_table = FindTable(tables, kLocaTableTag); - if (glyf_table == NULL || loca_table == NULL) { - return FONT_COMPRESSION_FAILURE(); - } - if (static_cast(glyf_table->dst_offset + glyf_table->dst_length) > - dst_length) { - return FONT_COMPRESSION_FAILURE(); - } - if (static_cast(loca_table->dst_offset + loca_table->dst_length) > - dst_length) { - return FONT_COMPRESSION_FAILURE(); - } - return ReconstructGlyf(transformed_buf, transformed_size, - dst + glyf_table->dst_offset, glyf_table->dst_length, - dst + loca_table->dst_offset, loca_table->dst_length); + return ReconstructTransformedGlyf(transformed_buf, transformed_size, + glyf_table, loca_table, dst, dst_length); } else if (tag == kLocaTableTag) { // processing was already done by glyf table, but validate if (!FindTable(tables, kGlyfTableTag)) { @@ -627,14 +583,70 @@ bool ReconstructTransformed(const std::vector
& tables, uint32_t tag, return true; } -uint32_t ComputeChecksum(const uint8_t* buf, size_t size) { - uint32_t checksum = 0; - for (size_t i = 0; i < size; i += 4) { - // We assume the addition is mod 2^32, which is valid because unsigned - checksum += (buf[i] << 24) | (buf[i + 1] << 16) | - (buf[i + 2] << 8) | buf[i + 3]; +uint32_t ComputeChecksum(const Table* table, const uint8_t* dst) { + return ComputeULongSum(dst + table->dst_offset, table->dst_length); +} + +const Table* FindTable(TtcFont ttc_font, const std::vector
& tables, + uint32_t tag) { + for (const auto i : ttc_font.table_indices) { + if (tables[i].tag == tag) return &tables[i]; } - return checksum; + return NULL; +} + +bool FixCollectionChecksums(size_t header_version, + const std::vector
& tables, const std::vector& ttc_fonts, + uint8_t* dst) { + size_t offset = CollectionHeaderSize(header_version, ttc_fonts.size()); + + for (const auto& ttc_font : ttc_fonts) { + offset += 12; // move to start of Offset Table + const std::vector& table_indices = ttc_font.table_indices; + + const Table* head_table = FindTable(ttc_font, tables, kHeadTableTag); + if (head_table == NULL || + head_table->dst_length < kCheckSumAdjustmentOffset + 4) { + return FONT_COMPRESSION_FAILURE(); + } + + size_t first_table_offset = std::numeric_limits::max(); + for (const auto index : table_indices) { + const auto& table = tables[index]; + if (table.dst_offset < first_table_offset) { + first_table_offset = table.dst_offset; + } + } + + size_t adjustment_offset = head_table->dst_offset + + kCheckSumAdjustmentOffset; + StoreU32(dst, adjustment_offset, 0); + + uint32_t file_checksum = 0; + // compute each tables checksum + for (auto i = 0; i < table_indices.size(); i++) { + const Table& table = tables[table_indices[i]]; + uint32_t table_checksum = ComputeChecksum(&table, dst); + size_t checksum_offset = offset + 4; // skip past tag to checkSum + + // write the checksum for the Table Record + StoreU32(dst, checksum_offset, table_checksum); + file_checksum += table_checksum; + // next Table Record + offset += 16; + } + + size_t header_size = kSfntHeaderSize + + kSfntEntrySize * table_indices.size(); + uint32_t header_checksum = ComputeULongSum(dst + ttc_font.dst_offset, + header_size); + + file_checksum += header_checksum; + uint32_t checksum_adjustment = 0xb1b0afba - file_checksum; + StoreU32(dst, adjustment_offset, checksum_adjustment); + } + + return true; } bool FixChecksums(const std::vector
& tables, uint8_t* dst) { @@ -648,15 +660,12 @@ bool FixChecksums(const std::vector
& tables, uint8_t* dst) { size_t n_tables = tables.size(); uint32_t file_checksum = 0; for (size_t i = 0; i < n_tables; ++i) { - const Table* table = &tables[i]; - size_t table_length = table->dst_length; - uint8_t* table_data = dst + table->dst_offset; - uint32_t checksum = ComputeChecksum(table_data, table_length); + uint32_t checksum = ComputeChecksum(&tables[i], dst); StoreU32(dst, kSfntHeaderSize + i * kSfntEntrySize + 4, checksum); file_checksum += checksum; } - file_checksum += ComputeChecksum(dst, - kSfntHeaderSize + kSfntEntrySize * n_tables); + file_checksum += ComputeULongSum(dst, + kSfntHeaderSize + kSfntEntrySize * n_tables); uint32_t checksum_adjustment = 0xb1b0afba - file_checksum; StoreU32(dst, adjustment_offset, checksum_adjustment); return true; @@ -673,7 +682,7 @@ bool Woff2Uncompress(uint8_t* dst_buf, size_t dst_size, return true; } -bool ReadShortDirectory(Buffer* file, std::vector
* tables, +bool ReadTableDirectory(Buffer* file, std::vector
* tables, size_t num_tables) { for (size_t i = 0; i < num_tables; ++i) { Table* table = &(*tables)[i]; @@ -732,6 +741,48 @@ size_t ComputeWOFF2FinalSize(const uint8_t* data, size_t length) { return total_length; } +// Writes a single Offset Table entry +size_t StoreOffsetTable(uint8_t* result, size_t offset, uint32_t flavor, + uint16_t num_tables) { + offset = StoreU32(result, offset, flavor); // sfnt version + offset = Store16(result, offset, num_tables); // num_tables + unsigned max_pow2 = 0; + while (1u << (max_pow2 + 1) <= num_tables) { + max_pow2++; + } + const uint16_t output_search_range = (1u << max_pow2) << 4; + offset = Store16(result, offset, output_search_range); // searchRange + offset = Store16(result, offset, max_pow2); // entrySelector + // rangeShift + offset = Store16(result, offset, (num_tables << 4) - output_search_range); + return offset; +} + +size_t StoreTableEntry(uint8_t* result, const Table& table, size_t offset) { + offset = StoreU32(result, offset, table.tag); + offset = StoreU32(result, offset, 0); // checksum, to fill in later + offset = StoreU32(result, offset, table.dst_offset); + offset = StoreU32(result, offset, table.dst_length); + return offset; +} + +// First table goes after all the headers, table directory, etc +uint64_t ComputeOffsetToFirstTable(const uint32_t header_version, + const uint16_t num_tables, + const std::vector& ttc_fonts) { + uint64_t offset = kSfntHeaderSize + + kSfntEntrySize * static_cast(num_tables); + if (header_version) { + offset = CollectionHeaderSize(header_version, ttc_fonts.size()) + + kSfntHeaderSize * ttc_fonts.size(); + for (const auto& ttc_font : ttc_fonts) { + offset += + kSfntEntrySize * ttc_font.table_indices.size(); + } + } + return offset; +} + bool ConvertWOFF2ToTTF(uint8_t* result, size_t result_length, const uint8_t* data, size_t length) { Buffer file(data, length); @@ -771,13 +822,83 @@ bool ConvertWOFF2ToTTF(uint8_t* result, size_t result_length, return FONT_COMPRESSION_FAILURE(); } std::vector
tables(num_tables); - // Note: change below to ReadLongDirectory to enable long format. - if (!ReadShortDirectory(&file, &tables, num_tables)) { + if (!ReadTableDirectory(&file, &tables, num_tables)) { return FONT_COMPRESSION_FAILURE(); } + + uint32_t header_version = 0; + // for each font in a ttc, metadata to use when rebuilding + std::vector ttc_fonts; + std::map loca_by_glyf; + + if (flavor == kTtcFontFlavor) { + if (!file.ReadU32(&header_version)) { + return FONT_COMPRESSION_FAILURE(); + } + uint32_t num_fonts; + if (!Read255UShort(&file, &num_fonts) || !num_fonts) { + return FONT_COMPRESSION_FAILURE(); + } + ttc_fonts.resize(num_fonts); + + for (auto i = 0; i < num_fonts; i++) { + TtcFont& ttc_font = ttc_fonts[i]; + uint32_t num_tables; + if (!Read255UShort(&file, &num_tables) || !num_tables) { + return FONT_COMPRESSION_FAILURE(); + } + if (!file.ReadU32(&ttc_font.flavor)) { + return FONT_COMPRESSION_FAILURE(); + } + + ttc_font.table_indices.resize(num_tables); + + const Table* glyf_table = NULL; + const Table* loca_table = NULL; + uint16_t glyf_idx; + uint16_t loca_idx; + + for (auto j = 0; j < num_tables; j++) { + unsigned int table_idx; + if (!Read255UShort(&file, &table_idx)) { + return FONT_COMPRESSION_FAILURE(); + } + ttc_font.table_indices[j] = table_idx; + + const Table& table = tables[table_idx]; + if (table.tag == kLocaTableTag) { + loca_table = &table; + loca_idx = table_idx; + } + if (table.tag == kGlyfTableTag) { + glyf_table = &table; + glyf_idx = table_idx; + } + + } + + if ((glyf_table == NULL) != (loca_table == NULL)) { + fprintf(stderr, "Cannot have just one of glyf/loca\n"); + return FONT_COMPRESSION_FAILURE(); + } + + if (glyf_table != NULL && loca_table != NULL) { + loca_by_glyf[glyf_table] = loca_table; + } + } + } + + const uint64_t first_table_offset = + ComputeOffsetToFirstTable(header_version, num_tables, ttc_fonts); + + if (first_table_offset > result_length) { + return FONT_COMPRESSION_FAILURE(); + } + uint64_t src_offset = file.offset(); - uint64_t dst_offset = kSfntHeaderSize + - kSfntEntrySize * static_cast(num_tables); + uint64_t dst_offset = first_table_offset; + + uint64_t uncompressed_sum = 0; for (uint16_t i = 0; i < num_tables; ++i) { Table* table = &tables[i]; @@ -787,7 +908,7 @@ bool ConvertWOFF2ToTTF(uint8_t* result, size_t result_length, if (src_offset > std::numeric_limits::max()) { return FONT_COMPRESSION_FAILURE(); } - src_offset = Round4(src_offset); // TODO: reconsider + src_offset = Round4(src_offset); table->dst_offset = dst_offset; dst_offset += table->dst_length; if (dst_offset > std::numeric_limits::max()) { @@ -805,38 +926,54 @@ bool ConvertWOFF2ToTTF(uint8_t* result, size_t result_length, return FONT_COMPRESSION_FAILURE(); } if (src_offset > length || dst_offset > result_length) { - return FONT_COMPRESSION_FAILURE(); - } - - const uint32_t sfnt_header_and_table_directory_size = 12 + 16 * num_tables; - if (sfnt_header_and_table_directory_size > result_length) { + fprintf(stderr, "offset fail; src_offset %lu length %lu " + "dst_offset %lu result_length %lu\n", + src_offset, length, dst_offset, result_length); return FONT_COMPRESSION_FAILURE(); } // Start building the font size_t offset = 0; - offset = StoreU32(result, offset, flavor); - offset = Store16(result, offset, num_tables); - unsigned max_pow2 = 0; - while (1u << (max_pow2 + 1) <= num_tables) { - max_pow2++; - } - const uint16_t output_search_range = (1u << max_pow2) << 4; - offset = Store16(result, offset, output_search_range); - offset = Store16(result, offset, max_pow2); - offset = Store16(result, offset, (num_tables << 4) - output_search_range); + size_t offset_table = 0; + if (header_version) { + // TTC header + offset = StoreU32(result, offset, flavor); // TAG TTCTag + offset = StoreU32(result, offset, header_version); // FIXED Version + offset = StoreU32(result, offset, ttc_fonts.size()); // ULONG numFonts + // Space for ULONG OffsetTable[numFonts] (zeroed initially) + offset_table = offset; // keep start of offset table for later + for (int i = 0; i < ttc_fonts.size(); i++) { + offset = StoreU32(result, offset, 0); // will fill real values in later + } + // space for DSIG fields for header v2 + if (header_version == 0x00020000) { + offset = StoreU32(result, offset, 0); // ULONG ulDsigTag + offset = StoreU32(result, offset, 0); // ULONG ulDsigLength + offset = StoreU32(result, offset, 0); // ULONG ulDsigOffset + } - // sort tags in the table directory in ascending alphabetical order - std::vector
sorted_tables(tables); - std::sort(sorted_tables.begin(), sorted_tables.end()); + // write Offset Tables and store the location of each in TTC Header + for (auto& ttc_font : ttc_fonts) { + // write Offset Table location into TTC Header + offset_table = StoreU32(result, offset_table, offset); - for (uint16_t i = 0; i < num_tables; ++i) { - const Table* table = &sorted_tables[i]; - offset = StoreU32(result, offset, table->tag); - offset = StoreU32(result, offset, 0); // checksum, to fill in later - offset = StoreU32(result, offset, table->dst_offset); - offset = StoreU32(result, offset, table->dst_length); + // write the actual offset table so our header doesn't lie + ttc_font.dst_offset = offset; + offset = StoreOffsetTable(result, offset, ttc_font.flavor, + ttc_font.table_indices.size()); + + // write table entries + for (const auto table_index : ttc_font.table_indices) { + offset = StoreTableEntry(result, tables[table_index], offset); + } + } + } else { + offset = StoreOffsetTable(result, offset, flavor, num_tables); + for (uint16_t i = 0; i < num_tables; ++i) { + offset = StoreTableEntry(result, tables[i], offset); + } } + std::vector uncompressed_buf; bool continue_valid = false; const uint8_t* transform_buf = NULL; @@ -879,12 +1016,26 @@ bool ConvertWOFF2ToTTF(uint8_t* result, size_t result_length, result_length) { return FONT_COMPRESSION_FAILURE(); } + std::memcpy(result + table->dst_offset, transform_buf, transform_length); } else { - if (!ReconstructTransformed(tables, table->tag, - transform_buf, transform_length, result, result_length)) { - return FONT_COMPRESSION_FAILURE(); + if (header_version) { + if (table->tag == kGlyfTableTag) { + const Table* loca_table = loca_by_glyf[table]; + if (!ReconstructTransformedGlyf(transform_buf, transform_length, + table, loca_table, result, result_length)) { + return FONT_COMPRESSION_FAILURE(); + } + } else if (table->tag != kLocaTableTag) { + // transform for this tag not known + return FONT_COMPRESSION_FAILURE(); + } + } else { + if (!ReconstructTransformed(tables, table->tag, + transform_buf, transform_length, result, result_length)) { + return FONT_COMPRESSION_FAILURE(); + } } } if (continue_valid) { @@ -895,7 +1046,17 @@ bool ConvertWOFF2ToTTF(uint8_t* result, size_t result_length, } } - return FixChecksums(sorted_tables, result); + if (header_version) { + if (!FixCollectionChecksums(header_version, tables, ttc_fonts, result)) { + return FONT_COMPRESSION_FAILURE(); + } + } else { + if (!FixChecksums(tables, result)) { + return FONT_COMPRESSION_FAILURE(); + } + } + + return true; } } // namespace woff2 diff --git a/src/woff2_enc.cc b/src/woff2_enc.cc index 7caeca7..d34f344 100644 --- a/src/woff2_enc.cc +++ b/src/woff2_enc.cc @@ -31,6 +31,7 @@ #include "./store_bytes.h" #include "./table_tags.h" #include "./transform.h" +#include "./variable_length.h" #include "./woff2_common.h" namespace woff2 { @@ -44,23 +45,6 @@ using std::vector; const size_t kWoff2HeaderSize = 48; const size_t kWoff2EntrySize = 20; -size_t Base128Size(size_t n) { - size_t size = 1; - for (; n >= 128; n >>= 7) ++size; - return size; -} - -void StoreBase128(size_t len, size_t* offset, uint8_t* dst) { - size_t size = Base128Size(len); - for (int i = 0; i < size; ++i) { - int b = static_cast((len >> (7 * (size - i - 1))) & 0x7f); - if (i < size - 1) { - b |= 0x80; - } - dst[(*offset)++] = b; - } -} - bool Compress(const uint8_t* data, const size_t len, uint8_t* result, uint32_t* result_len, brotli::BrotliParams::Mode mode) { @@ -87,21 +71,6 @@ bool TextCompress(const uint8_t* data, const size_t len, brotli::BrotliParams::MODE_TEXT); } -bool ReadLongDirectory(Buffer* file, std::vector
* tables, - size_t num_tables) { - for (size_t i = 0; i < num_tables; ++i) { - Table* table = &(*tables)[i]; - if (!file->ReadU32(&table->tag) || - !file->ReadU32(&table->flags) || - !file->ReadU32(&table->src_length) || - !file->ReadU32(&table->transform_length) || - !file->ReadU32(&table->dst_length)) { - return FONT_COMPRESSION_FAILURE(); - } - } - return true; -} - int KnownTableIndex(uint32_t tag) { for (int i = 0; i < 63; ++i) { if (tag == kKnownTags[i]) return i; @@ -134,16 +103,42 @@ size_t TableEntrySize(const Table& table) { return size; } -size_t ComputeWoff2Length(const std::vector
& tables, +size_t ComputeWoff2Length(const FontCollection& font_collection, + const std::vector
& tables, + std::map index_by_offset, size_t extended_metadata_length) { size_t size = kWoff2HeaderSize; + for (const auto& table : tables) { size += TableEntrySize(table); } + + // for collections only, collection tables + if (font_collection.fonts.size() > 1) { + size += 4; // UInt32 Version of TTC Header + size += Size255UShort(font_collection.fonts.size()); // 255UInt16 numFonts + + size += 4 * font_collection.fonts.size(); // UInt32 flavor for each + + for (const auto& font : font_collection.fonts) { + size += Size255UShort(font.tables.size()); // 255UInt16 numTables + for (const auto& entry : font.tables) { + const Font::Table& table = entry.second; + // no collection entry for xform table + if (table.tag & 0x80808080) continue; + + uint16_t table_index = index_by_offset[table.offset]; + size += Size255UShort(table_index); // 255UInt16 index entry + } + } + } + + // compressed data for (const auto& table : tables) { size += table.dst_length; size = Round4(size); } + size += extended_metadata_length; return size; } @@ -156,10 +151,37 @@ size_t ComputeTTFLength(const std::vector
& tables) { return size; } +size_t ComputeUncompressedLength(const Font& font) { + // sfnt header + offset table + size_t size = 12 + 16 * font.num_tables; + for (const auto& entry : font.tables) { + const Font::Table& table = entry.second; + if (table.tag & 0x80808080) continue; // xform tables don't stay + if (table.IsReused()) continue; // don't have to pay twice + size += Round4(table.length); + } + return size; +} + +size_t ComputeUncompressedLength(const FontCollection& font_collection) { + if (font_collection.fonts.size() == 1) { + return ComputeUncompressedLength(font_collection.fonts[0]); + } + size_t size = CollectionHeaderSize(font_collection.header_version, + font_collection.fonts.size()); + for (const auto& font : font_collection.fonts) { + size += ComputeUncompressedLength(font); + } + return size; +} + size_t ComputeTotalTransformLength(const Font& font) { size_t total = 0; for (const auto& i : font.tables) { const Font::Table& table = i.second; + if (table.IsReused()) { + continue; + } if (table.tag & 0x80808080 || !font.FindTable(table.tag ^ 0x80808080)) { // Count transformed tables and non-transformed tables that do not have // transformed versions. @@ -193,28 +215,31 @@ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, return ConvertTTFToWOFF2(data, length, result, result_length, ""); } +bool TransformFontCollection(FontCollection* font_collection) { + for (auto& font : font_collection->fonts) { + if (!TransformGlyfAndLocaTables(&font)) { + fprintf(stderr, "Font transformation failed.\n"); + return false; + } + } + + return true; +} + bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, uint8_t *result, size_t *result_length, const string& extended_metadata) { - Font font; - if (!ReadFont(data, length, &font)) { + FontCollection font_collection; + if (!ReadFontCollection(data, length, &font_collection)) { fprintf(stderr, "Parsing of the input font failed.\n"); return false; } - if (!NormalizeFont(&font)) { - fprintf(stderr, "Font normalization failed.\n"); + if (!NormalizeFontCollection(&font_collection)) { return false; } - if (!TransformGlyfAndLocaTables(&font)) { - fprintf(stderr, "Font transformation failed.\n"); - return false; - } - - const Font::Table* head_table = font.FindTable(kHeadTableTag); - if (head_table == NULL) { - fprintf(stderr, "Missing head table.\n"); + if (!TransformFontCollection(&font_collection)) { return false; } @@ -223,7 +248,11 @@ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, // buffer for the compressor, since the compressor can potentially increase // the size. If the compressor overflows this, it should return false and // then this function will also return false. - size_t total_transform_length = ComputeTotalTransformLength(font); + + size_t total_transform_length = 0; + for (const auto& font : font_collection.fonts) { + total_transform_length += ComputeTotalTransformLength(font); + } size_t compression_buffer_size = CompressedBufferSize(total_transform_length); std::vector compression_buf(compression_buffer_size); uint32_t total_compressed_length = compression_buffer_size; @@ -231,12 +260,16 @@ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, // Collect all transformed data into one place. std::vector transform_buf(total_transform_length); size_t transform_offset = 0; - for (const auto& i : font.tables) { - if (i.second.tag & 0x80808080) continue; - const Font::Table* table = font.FindTable(i.second.tag ^ 0x80808080); - if (table == NULL) table = &i.second; - StoreBytes(table->data, table->length, - &transform_offset, &transform_buf[0]); + for (const auto& font : font_collection.fonts) { + for (const auto& i : font.tables) { + const Font::Table* table = font.FindTable(i.second.tag ^ 0x80808080); + if (i.second.IsReused()) continue; + if (i.second.tag & 0x80808080) continue; + + if (table == NULL) table = &i.second; + StoreBytes(table->data, table->length, + &transform_offset, &transform_buf[0]); + } } // Compress all transformed data in one stream. if (!Woff2Compress(transform_buf.data(), total_transform_length, @@ -247,6 +280,7 @@ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, } // Compress the extended metadata + // TODO(user): how does this apply to collections uint32_t compressed_metadata_buf_length = CompressedBufferSize(extended_metadata.length()); std::vector compressed_metadata_buf(compressed_metadata_buf_length); @@ -264,39 +298,51 @@ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, } std::vector
tables; - for (const auto& i : font.tables) { - const Font::Table& src_table = i.second; - if (src_table.tag & 0x80808080) { - // This is a transformed table, we will write it together with the - // original version. - continue; + std::map index_by_offset; + + for (const auto& font : font_collection.fonts) { + + for (const auto tag : font.OutputOrderedTags()) { + const Font::Table& src_table = font.tables.at(tag); + + if (index_by_offset.find(src_table.offset) == index_by_offset.end()) { + index_by_offset[src_table.offset] = index_by_offset.size(); + } else if (!src_table.IsReused()) { + return false; + } + + + if (src_table.IsReused()) { + continue; + } + + Table table; + table.tag = src_table.tag; + table.flags = 0; + table.src_length = src_table.length; + table.transform_length = src_table.length; + const uint8_t* transformed_data = src_table.data; + const Font::Table* transformed_table = + font.FindTable(src_table.tag ^ 0x80808080); + if (transformed_table != NULL) { + table.flags |= kWoff2FlagsTransform; + table.transform_length = transformed_table->length; + transformed_data = transformed_table->data; + } + if (tables.empty()) { + table.dst_length = total_compressed_length; + table.dst_data = &compression_buf[0]; + } else { + table.dst_length = 0; + table.dst_data = NULL; + table.flags |= kWoff2FlagsContinueStream; + } + tables.push_back(table); } - Table table; - table.tag = src_table.tag; - table.flags = 0; - table.src_length = src_table.length; - table.transform_length = src_table.length; - const uint8_t* transformed_data = src_table.data; - const Font::Table* transformed_table = - font.FindTable(src_table.tag ^ 0x80808080); - if (transformed_table != NULL) { - table.flags |= kWoff2FlagsTransform; - table.transform_length = transformed_table->length; - transformed_data = transformed_table->data; - } - if (tables.empty()) { - table.dst_length = total_compressed_length; - table.dst_data = &compression_buf[0]; - } else { - table.dst_length = 0; - table.dst_data = NULL; - table.flags |= kWoff2FlagsContinueStream; - } - tables.push_back(table); } - size_t woff2_length = - ComputeWoff2Length(tables, compressed_metadata_buf_length); + size_t woff2_length = ComputeWoff2Length(font_collection, tables, + index_by_offset, compressed_metadata_buf_length); if (woff2_length > *result_length) { fprintf(stderr, "Result allocation was too small (%zd vs %zd bytes).\n", *result_length, woff2_length); @@ -304,15 +350,26 @@ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, } *result_length = woff2_length; + const Font& first_font = font_collection.fonts[0]; size_t offset = 0; + + // start of woff2 header (http://www.w3.org/TR/WOFF2/#woff20Header) StoreU32(kWoff2Signature, &offset, result); - StoreU32(font.flavor, &offset, result); + if (font_collection.fonts.size() == 1) { + StoreU32(first_font.flavor, &offset, result); + } else { + StoreU32(kTtcFontFlavor, &offset, result); + } StoreU32(woff2_length, &offset, result); Store16(tables.size(), &offset, result); Store16(0, &offset, result); // reserved - StoreU32(ComputeTTFLength(tables), &offset, result); - StoreU32(total_compressed_length, &offset, result); - StoreBytes(head_table->data + 4, 4, &offset, result); // font revision + // totalSfntSize + StoreU32(ComputeUncompressedLength(font_collection), &offset, result); + StoreU32(total_compressed_length, &offset, result); // totalCompressedSize + + // TODO(user): is always taking this from the first tables head OK? + // font revision + StoreBytes(first_font.FindTable(kHeadTableTag)->data + 4, 4, &offset, result); if (compressed_metadata_buf_length > 0) { StoreU32(woff2_length - compressed_metadata_buf_length, &offset, result); // metaOffset @@ -325,9 +382,51 @@ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length, } StoreU32(0, &offset, result); // privOffset StoreU32(0, &offset, result); // privLength + // end of woff2 header + + // table directory (http://www.w3.org/TR/WOFF2/#table_dir_format) for (const auto& table : tables) { StoreTableEntry(table, &offset, result); } + + // for collections only, collection table directory + if (font_collection.fonts.size() > 1) { + StoreU32(font_collection.header_version, &offset, result); + Store255UShort(font_collection.fonts.size(), &offset, result); + for (const Font& font : font_collection.fonts) { + + uint16_t num_tables = 0; + for (const auto& entry : font.tables) { + const Font::Table& table = entry.second; + if (table.tag & 0x80808080) continue; // don't write xform tables + num_tables++; + } + Store255UShort(num_tables, &offset, result); + + StoreU32(font.flavor, &offset, result); + for (const auto& entry : font.tables) { + const Font::Table& table = entry.second; + if (table.tag & 0x80808080) continue; // don't write xform tables + + // for reused tables, only the original has an updated offset + uint32_t table_offset = + table.IsReused() ? table.reuse_of->offset : table.offset; + uint32_t table_length = + table.IsReused() ? table.reuse_of->length : table.length; + if (index_by_offset.find(table_offset) == index_by_offset.end()) { + fprintf(stderr, "Missing table index for offset 0x%08x\n", + table_offset); + return false; + } + uint16_t index = index_by_offset[table_offset]; + Store255UShort(index, &offset, result); + + } + + } + } + + // compressed data format (http://www.w3.org/TR/WOFF2/#table_format) for (const auto& table : tables) { StoreBytes(table.dst_data, table.dst_length, &offset, result); offset = Round4(offset);