Adding TTC support, take 1

This commit is contained in:
Rod Sheeter
2015-02-24 13:53:35 -08:00
parent 445f541996
commit 1c975530b9
11 changed files with 1040 additions and 279 deletions
+221 -29
View File
@@ -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<uint32_t> Font::OutputOrderedTags() const {
std::vector<uint32_t> 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<uint32_t, uint32_t> 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<uint32_t, Font::Table*>* 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<uint32_t> 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<Font>::iterator font_it = font_collection->fonts.begin();
std::map<uint32_t, Font::Table*> 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) {
+30 -1
View File
@@ -41,25 +41,54 @@ struct Font {
// Buffer used to mutate the data before writing out.
std::vector<uint8_t> 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<uint32_t, Table> tables;
std::vector<uint32_t> 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<uint32_t, Font::Table*> tables;
std::vector<Font> 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
+70 -17
View File
@@ -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
+2
View File
@@ -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
+12 -18
View File
@@ -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<uint8_t>* out, int value) {
out->push_back(value & 255);
}
void Write255UShort(std::vector<uint8_t>* 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];
+133
View File
@@ -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<uint8_t>* 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<uint8_t> 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<int>((len >> (7 * (size - i - 1))) & 0x7f);
if (i < size - 1) {
b |= 0x80;
}
dst[(*offset)++] = b;
}
}
} // namespace woff2
+38
View File
@@ -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 <inttypes.h>
#include <vector>
#include "./buffer.h"
namespace woff2 {
size_t Size255UShort(uint16_t value);
bool Read255UShort(Buffer* buf, unsigned int* value);
void Write255UShort(std::vector<uint8_t>* 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_
+46
View File
@@ -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 <algorithm>
#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
+18 -4
View File
@@ -17,8 +17,11 @@
#ifndef WOFF2_WOFF2_COMMON_H_
#define WOFF2_WOFF2_COMMON_H_
#include <stddef.h>
#include <inttypes.h>
#include <string>
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_
+285 -124
View File
@@ -21,14 +21,15 @@
#include <cstring>
#include <limits>
#include <string>
#include <algorithm>
#include <vector>
#include <map>
#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<uint16_t> 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<uint32_t>& 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<uint32_t>& 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<Table>& 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<uint64_t>(glyf_table->dst_offset + glyf_table->dst_length) >
dst_length) {
return FONT_COMPRESSION_FAILURE();
}
if (static_cast<uint64_t>(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<Table>& 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<uint64_t>(glyf_table->dst_offset + glyf_table->dst_length) >
dst_length) {
return FONT_COMPRESSION_FAILURE();
}
if (static_cast<uint64_t>(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<Table>& 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<Table>& 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<Table>& tables, const std::vector<TtcFont>& 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<uint16_t>& 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<size_t>::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<Table>& tables, uint8_t* dst) {
@@ -648,15 +660,12 @@ bool FixChecksums(const std::vector<Table>& 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<Table>* tables,
bool ReadTableDirectory(Buffer* file, std::vector<Table>* 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<TtcFont>& ttc_fonts) {
uint64_t offset = kSfntHeaderSize +
kSfntEntrySize * static_cast<uint64_t>(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<Table> 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<TtcFont> ttc_fonts;
std::map<const Table*, const Table*> 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<uint64_t>(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<uint32_t>::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<uint32_t>::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<Table> 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<uint8_t> 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
+185 -86
View File
@@ -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<int>((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<Table>* 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<Table>& tables,
size_t ComputeWoff2Length(const FontCollection& font_collection,
const std::vector<Table>& tables,
std::map<uint32_t, uint16_t> 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<Table>& 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<uint8_t> 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<uint8_t> 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<uint8_t> compressed_metadata_buf(compressed_metadata_buf_length);
@@ -264,39 +298,51 @@ bool ConvertTTFToWOFF2(const uint8_t *data, size_t length,
}
std::vector<Table> 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<uint32_t, uint16_t> 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);