diff options
author | Roderick Sheeter <rsheeter@google.com> | 2013-11-20 15:11:17 -0800 |
---|---|---|
committer | Roderick Sheeter <rsheeter@google.com> | 2013-11-20 15:11:17 -0800 |
commit | 4147ca26463c01f4309d0cd63c617c4876b9ba3e (patch) | |
tree | a2b6dbf747aaef808249792e61edcb99daac1438 | |
parent | 437bbad37074e472b66d427814275de84ca77f19 (diff) | |
download | src-4147ca26463c01f4309d0cd63c617c4876b9ba3e.tar.gz |
Remove old files; use the ones in woff2/ instead
27 files changed, 0 insertions, 2935 deletions
diff --git a/cpp/.SConstruct.swp b/cpp/.SConstruct.swp Binary files differdeleted file mode 100644 index 402a853..0000000 --- a/cpp/.SConstruct.swp +++ /dev/null diff --git a/cpp/Inconsolata-Regular.woff2 b/cpp/Inconsolata-Regular.woff2 Binary files differdeleted file mode 100644 index ddfefb3..0000000 --- a/cpp/Inconsolata-Regular.woff2 +++ /dev/null diff --git a/cpp/README b/cpp/README deleted file mode 100644 index 6c40119..0000000 --- a/cpp/README +++ /dev/null @@ -1,44 +0,0 @@ -To build: - -Check out a recent version of OTS, so that ots-read-only/ is alongside cpp/ - -Cd into cpp, and run "scons". - -Then, decompress a font using ./woff2-decompress font.wof2 > font.ttf . - -An example font is provided (Inconsolata-Regular.wof2). In this snapshot, -it contains no actual compression, but does apply the glyf table transform -and has the file format and structure as described in the current draft -of the "WOFF Ultra Condensed file format" doc. - -That said, it is possible to get reliable estimates, and at the very least, -bounds, on the compression efficiency, with the confidence that the -compression is reversible. Running the compressor and doing a whole-file -compression with a standard entropy coder such as gzip or lzma will yield -a file size within a few dozen bytes or so of using a single entropy coded -stream in the final file format. - -Another limitation of the current implementation snapshot is that it -doesn't implement continue streams. These will follow shortly. - -= Building with lzma = - -The lzma-enabled build is made with gyp instead of scons. Right now, the build -requires patching a clean copy of OTS. (And thanks to Bashi for the patch!) - -- Download GYP from http://code.google.com/p/gyp/ -- Download clean OTS sources: -% svn checkout http://ots.googlecode.com/svn/trunk/ ots-read-only -- Apply patch -% cd ots-read-only; patch -p0 < ../ots-lzma.patch -- Run gyp to generate Makefile -% cd ../cpp; gyp --depth=. -f make woff2.gyp -- Build -% make - -Now run: - -out/Default/woff2-decompress Inconsolata-Regular-lzma.wof2 > i.ttf - -We expect the build recipes to be cleaned up before the code is ready for -production, but this should be good enough for testing. diff --git a/cpp/SConstruct b/cpp/SConstruct deleted file mode 100644 index 4ec2e45..0000000 --- a/cpp/SConstruct +++ /dev/null @@ -1,52 +0,0 @@ -# Build script for Linux -# Note: this script DOESN'T include LZMA. Use the gyp-based build instead. -# -# Usage: -# $ cd ots/test/ -# $ scons -c # clean -# $ scons # build -# - -# Since the validator-checker tool might handle malicious font files, all hardening options for recent g++/ld are enabled just in case. -# See http://wiki.debian.org/Hardening for details. -env = Environment(CCFLAGS = ['-O2', '-I../ots-read-only/include', '-I../ots-read-only/src', '-I/usr/include/freetype2', '-ggdb', '-Wall', '-W', '-Wno-unused-parameter', '-fno-strict-aliasing', '-fPIE', '-fstack-protector', '-D_FORTIFY_SOURCE=2', '-DOTS_DEBUG'], LINKFLAGS = ['-ggdb', '-Wl,-z,relro', '-Wl,-z,now', '-pie', '-lz']) - -env.Library('libwoff2.a', - [ - 'woff2.cc', - - # Just build all of OTS to keep things simple for now. We could - # refactor so that we only compile the few support routines from - # ots.cc that we need. - '../ots-read-only/src/cff.cc', - '../ots-read-only/src/cff_type2_charstring.cc', - '../ots-read-only/src/cmap.cc', - '../ots-read-only/src/cvt.cc', - '../ots-read-only/src/fpgm.cc', - '../ots-read-only/src/gasp.cc', - '../ots-read-only/src/gdef.cc', - '../ots-read-only/src/glyf.cc', - '../ots-read-only/src/gpos.cc', - '../ots-read-only/src/gsub.cc', - '../ots-read-only/src/hdmx.cc', - '../ots-read-only/src/head.cc', - '../ots-read-only/src/hhea.cc', - '../ots-read-only/src/hmtx.cc', - '../ots-read-only/src/kern.cc', - '../ots-read-only/src/layout.cc', - '../ots-read-only/src/loca.cc', - '../ots-read-only/src/ltsh.cc', - '../ots-read-only/src/maxp.cc', - '../ots-read-only/src/metrics.cc', - '../ots-read-only/src/name.cc', - '../ots-read-only/src/os2.cc', - '../ots-read-only/src/ots.cc', - '../ots-read-only/src/post.cc', - '../ots-read-only/src/prep.cc', - '../ots-read-only/src/vdmx.cc', - '../ots-read-only/src/vhea.cc', - '../ots-read-only/src/vmtx.cc', - '../ots-read-only/src/vorg.cc' - ]) - -env.Program('woff2-decompress.cc', LIBS = ['woff2'], LIBPATH='.') diff --git a/cpp/woff2-decompress.cc b/cpp/woff2-decompress.cc deleted file mode 100644 index dc9a281..0000000 --- a/cpp/woff2-decompress.cc +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// A very simple commandline tool for decompressing woff2 format files -// (given as argc[1]), writing the decompressed version to stdout. - -#include <fcntl.h> -#include <sys/stat.h> -#include <unistd.h> - -#include <cstdio> -#include <cstdlib> - -#include "opentype-sanitiser.h" -#include "woff2.h" - -namespace { - -int Usage(const char *argv0) { - std::fprintf(stderr, "Usage: %s woff2_file > dest_ttf_file\n", argv0); - return 1; -} - -} // namespace - -int main(int argc, char **argv) { - if (argc != 2) return Usage(argv[0]); - if (::isatty(1)) return Usage(argv[0]); - - const int fd = ::open(argv[1], O_RDONLY); - if (fd < 0) { - ::perror("open"); - return 1; - } - - struct stat st; - ::fstat(fd, &st); - - uint8_t *data = new uint8_t[st.st_size]; - if (::read(fd, data, st.st_size) != st.st_size) { - ::perror("read"); - return 1; - } - ::close(fd); - - size_t decompressed_size = ots::ComputeWOFF2FinalSize(data, st.st_size); - if (decompressed_size == 0) { - std::fprintf(stderr, "Error computing decompressed file size!\n"); - return 1; - } - uint8_t *buf = new uint8_t[decompressed_size]; - const bool result = ots::ConvertWOFF2ToTTF(buf, decompressed_size, - data, st.st_size); - - if (!result) { - std::fprintf(stderr, "Failed to decompress file!\n"); - } - fwrite(buf, 1, decompressed_size, stdout); - return !result; -} diff --git a/cpp/woff2.cc b/cpp/woff2.cc deleted file mode 100644 index 6fe543c..0000000 --- a/cpp/woff2.cc +++ /dev/null @@ -1,1035 +0,0 @@ -// Copyright (c) 2012 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This is the implementation of decompression of the proposed WOFF Ultra -// Condensed file format. - -// For now, use of LZMA is conditional, because the build is trickier. When -// that gets all sorted out, we can get rid of these ifdefs. -#define USE_LZMA - -#include <vector> - -#ifdef USE_LZMA -#include "third_party/lzma_sdk/LzmaLib.h" -#endif - -#include <zlib.h> - -#include "opentype-sanitiser.h" -#include "ots-memory-stream.h" - -#include "ots.h" -#include "woff2.h" - -namespace { - -// simple glyph flags -const int kGlyfOnCurve = 1 << 0; -const int kGlyfXShort = 1 << 1; -const int kGlyfYShort = 1 << 2; -const int kGlyfRepeat = 1 << 3; -const int kGlyfThisXIsSame = 1 << 4; -const int kGlyfThisYIsSame = 1 << 5; - -// composite glyph flags -const int FLAG_ARG_1_AND_2_ARE_WORDS = 1 << 0; -const int FLAG_ARGS_ARE_XY_VALUES = 1 << 1; -const int FLAG_ROUND_XY_TO_GRID = 1 << 2; -const int FLAG_WE_HAVE_A_SCALE = 1 << 3; -const int FLAG_RESERVED = 1 << 4; -const int FLAG_MORE_COMPONENTS = 1 << 5; -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 int FLAG_USE_MY_METRICS = 1 << 9; -const int FLAG_OVERLAP_COMPOUND = 1 << 10; -const int FLAG_SCALED_COMPONENT_OFFSET = 1 << 11; -const int FLAG_UNSCALED_COMPONENT_OFFSET = 1 << 12; - -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; - -// Note that the byte order is big-endian, not the same as ots.cc -#define TAG(a, b, c, d) ((a << 24) | (b << 16) | (c << 8) | d) - -const unsigned int kWoff2FlagsContinueStream = 1 << 4; -const unsigned int kWoff2FlagsTransform = 1 << 5; - -const size_t kWoff2HeaderSize = 44; -const size_t kWoff2EntrySize = 20; - -const size_t kLzmaHeaderSize = 13; - -// Compression type values common to both short and long formats -const uint32_t kCompressionTypeMask = 0xf; -const uint32_t kCompressionTypeNone = 0; -const uint32_t kCompressionTypeGzip = 1; -const uint32_t kCompressionTypeLzma = 2; - -// This is a special value for the short format only, as described in -// "Design for compressed header format" in draft doc. -const uint32_t kShortFlagsContinue = 3; - -struct Point { - int x; - int y; - bool on_curve; -}; - -struct Table { - uint32_t tag; - uint32_t flags; - uint32_t src_offset; - uint32_t src_length; - - uint32_t transform_length; - - uint32_t dst_offset; - uint32_t dst_length; -}; - -// TODO: copied from ots.cc, probably shouldn't be duplicated. -// Round a value up to the nearest multiple of 4. Don't round the value in the -// case that rounding up overflows. -template<typename T> T Round4(T value) { - if (std::numeric_limits<T>::max() - value < 3) { - return value; - } - return (value + 3) & ~3; -} - -// Based on section 6.1.1 of MicroType Express draft spec -bool Read255UShort(ots::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 OTS_FAILURE(); - } - if (code == kWordCode) { - uint16_t result = 0; - if (!buf->ReadU16(&result)) { - return OTS_FAILURE(); - } - *value = result; - return true; - } else if (code == kOneMoreByteCode1) { - uint8_t result = 0; - if (!buf->ReadU8(&result)) { - return OTS_FAILURE(); - } - *value = result + kLowestUCode; - return true; - } else if (code == kOneMoreByteCode2) { - uint8_t result = 0; - if (!buf->ReadU8(&result)) { - return OTS_FAILURE(); - } - *value = result + kLowestUCode * 2; - return true; - } else { - *value = code; - return true; - } -} - -bool ReadBase128(ots::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 OTS_FAILURE(); - } - // If any of the top seven bits are set then we're about to overflow. - if (result & 0xe0000000) { - return OTS_FAILURE(); - } - result = (result << 7) | (code & 0x7f); - if ((code & 0x80) == 0) { - *value = result; - return true; - } - } - // Make sure not to exceed the size bound - return OTS_FAILURE(); -} - -size_t StoreU32(uint8_t* dst, size_t offset, uint32_t x) { - dst[offset] = x >> 24; - dst[offset + 1] = x >> 16; - dst[offset + 2] = x >> 8; - dst[offset + 3] = x; - return offset + 4; -} - -size_t Store16(uint8_t* dst, size_t offset, int x) { - dst[offset] = x >> 8; - dst[offset + 1] = x; - return offset + 2; -} - -int WithSign(int flag, int baseval) { - // Precondition: 0 <= baseval < 65536 (to avoid integer overflow) - return (flag & 1) ? baseval : -baseval; -} - -bool TripletDecode(const uint8_t* flags_in, const uint8_t* in, size_t in_size, - unsigned int n_points, std::vector<Point>* result, - size_t* in_bytes_consumed) { - int x = 0; - int y = 0; - - if (n_points > in_size) { - return OTS_FAILURE(); - } - unsigned int triplet_index = 0; - - for (unsigned int i = 0; i < n_points; ++i) { - uint8_t flag = flags_in[i]; - bool on_curve = !(flag >> 7); - flag &= 0x7f; - unsigned int n_data_bytes; - if (flag < 84) { - n_data_bytes = 1; - } else if (flag < 120) { - n_data_bytes = 2; - } else if (flag < 124) { - n_data_bytes = 3; - } else { - n_data_bytes = 4; - } - if (triplet_index + n_data_bytes > in_size || - triplet_index + n_data_bytes < triplet_index) { - return OTS_FAILURE(); - } - int dx, dy; - if (flag < 10) { - dx = 0; - dy = WithSign(flag, ((flag & 14) << 7) + in[triplet_index]); - } else if (flag < 20) { - dx = WithSign(flag, (((flag - 10) & 14) << 7) + in[triplet_index]); - dy = 0; - } else if (flag < 84) { - int b0 = flag - 20; - int b1 = in[triplet_index]; - dx = WithSign(flag, 1 + (b0 & 0x30) + (b1 >> 4)); - dy = WithSign(flag >> 1, 1 + ((b0 & 0x0c) << 2) + (b1 & 0x0f)); - } else if (flag < 120) { - int b0 = flag - 84; - dx = WithSign(flag, 1 + ((b0 / 12) << 8) + in[triplet_index]); - dy = WithSign(flag >> 1, - 1 + (((b0 % 12) >> 2) << 8) + in[triplet_index + 1]); - } else if (flag < 124) { - int b2 = in[triplet_index + 1]; - dx = WithSign(flag, (in[triplet_index] << 4) + (b2 >> 4)); - dy = WithSign(flag >> 1, ((b2 & 0x0f) << 8) + in[triplet_index + 2]); - } else { - dx = WithSign(flag, (in[triplet_index] << 8) + in[triplet_index + 1]); - dy = WithSign(flag >> 1, - (in[triplet_index + 2] << 8) + in[triplet_index + 3]); - } - triplet_index += n_data_bytes; - // Possible overflow but coordinate values are not security sensitive - x += dx; - y += dy; - result->push_back(Point()); - Point& back = result->back(); - back.x = x; - back.y = y; - back.on_curve = on_curve; - } - *in_bytes_consumed = triplet_index; - return true; -} - -// This function stores just the point data. On entry, dst points to the -// beginning of a simple glyph. Returns true on success. -bool StorePoints(const std::vector<Point>& points, - unsigned int n_contours, unsigned int instruction_length, - uint8_t* dst, size_t dst_size, size_t* glyph_size) { - // I believe that n_contours < 65536, in which case this is safe. However, a - // comment and/or an assert would be good. - unsigned int flag_offset = kEndPtsOfContoursOffset + 2 * n_contours + 2 + - instruction_length; - int last_flag = -1; - int repeat_count = 0; - int last_x = 0; - int last_y = 0; - unsigned int x_bytes = 0; - unsigned int y_bytes = 0; - - for (unsigned int i = 0; i < points.size(); ++i) { - const Point& point = points[i]; - int flag = point.on_curve ? kGlyfOnCurve : 0; - int dx = point.x - last_x; - int dy = point.y - last_y; - if (dx == 0) { - flag |= kGlyfThisXIsSame; - } else if (dx > -256 && dx < 256) { - flag |= kGlyfXShort | (dx > 0 ? kGlyfThisXIsSame : 0); - x_bytes += 1; - } else { - x_bytes += 2; - } - if (dy == 0) { - flag |= kGlyfThisYIsSame; - } else if (dy > -256 && dy < 256) { - flag |= kGlyfYShort | (dy > 0 ? kGlyfThisYIsSame : 0); - y_bytes += 1; - } else { - y_bytes += 2; - } - - if (flag == last_flag && repeat_count != 255) { - dst[flag_offset - 1] |= kGlyfRepeat; - repeat_count++; - } else { - if (repeat_count != 0) { - if (flag_offset >= dst_size) { - return OTS_FAILURE(); - } - dst[flag_offset++] = repeat_count; - } - if (flag_offset >= dst_size) { - return OTS_FAILURE(); - } - dst[flag_offset++] = flag; - repeat_count = 0; - } - last_x = point.x; - last_y = point.y; - last_flag = flag; - } - - if (repeat_count != 0) { - if (flag_offset >= dst_size) { - return OTS_FAILURE(); - } - dst[flag_offset++] = repeat_count; - } - unsigned int xy_bytes = x_bytes + y_bytes; - if (xy_bytes < x_bytes || - flag_offset + xy_bytes < flag_offset || - flag_offset + xy_bytes > dst_size) { - return OTS_FAILURE(); - } - - int x_offset = flag_offset; - int y_offset = flag_offset + x_bytes; - last_x = 0; - last_y = 0; - for (unsigned int i = 0; i < points.size(); ++i) { - int dx = points[i].x - last_x; - if (dx == 0) { - // pass - } else if (dx > -256 && dx < 256) { - dst[x_offset++] = std::abs(dx); - } else { - // will always fit for valid input, but overflow is harmless - x_offset = Store16(dst, x_offset, dx); - } - last_x += dx; - int dy = points[i].y - last_y; - if (dy == 0) { - // pass - } else if (dy > -256 && dy < 256) { - dst[y_offset++] = std::abs(dy); - } else { - y_offset = Store16(dst, y_offset, dy); - } - last_y += dy; - } - *glyph_size = y_offset; - return true; -} - -// Compute the bounding box of the coordinates, and store into a glyf buffer. -// A precondition is that there are at least 10 bytes available. -void ComputeBbox(const std::vector<Point>& points, uint8_t* dst) { - int x_min = 0; - int y_min = 0; - int x_max = 0; - int y_max = 0; - - for (unsigned int i = 0; i < points.size(); ++i) { - int x = points[i].x; - int y = points[i].y; - if (i == 0 || x < x_min) x_min = x; - if (i == 0 || x > x_max) x_max = x; - if (i == 0 || y < y_min) y_min = y; - if (i == 0 || y > y_max) y_max = y; - } - size_t offset = 2; - offset = Store16(dst, offset, x_min); - offset = Store16(dst, offset, y_min); - offset = Store16(dst, offset, x_max); - offset = Store16(dst, offset, y_max); -} - -// Process entire bbox stream. This is done as a separate pass to allow for -// composite bbox computations (an optional more aggressive transform). -bool ProcessBboxStream(ots::Buffer* bbox_stream, unsigned int n_glyphs, - const std::vector<uint32_t>& loca_values, uint8_t* glyf_buf, - size_t glyf_buf_length) { - const uint8_t* buf = bbox_stream->buffer(); - if (n_glyphs >= 65536 || loca_values.size() != n_glyphs + 1) { - return OTS_FAILURE(); - } - // Safe because n_glyphs is bounded - unsigned int bitmap_length = ((n_glyphs + 31) >> 5) << 2; - if (!bbox_stream->Skip(bitmap_length)) { - return OTS_FAILURE(); - } - for (unsigned int i = 0; i < n_glyphs; ++i) { - if (buf[i >> 3] & (0x80 >> (i & 7))) { - uint32_t loca_offset = loca_values[i]; - if (loca_values[i + 1] - loca_offset < kEndPtsOfContoursOffset) { - return OTS_FAILURE(); - } - if (glyf_buf_length < 2 + 10 || - loca_offset > glyf_buf_length - 2 - 10) { - return OTS_FAILURE(); - } - if (!bbox_stream->Read(glyf_buf + loca_offset + 2, 8)) { - return OTS_FAILURE(); - } - } - } - return true; -} - -bool ProcessComposite(ots::Buffer* composite_stream, uint8_t* dst, - size_t dst_size, size_t* glyph_size, bool* have_instructions) { - size_t start_offset = composite_stream->offset(); - bool we_have_instructions = false; - - uint16_t flags = FLAG_MORE_COMPONENTS; - while (flags & FLAG_MORE_COMPONENTS) { - if (!composite_stream->ReadU16(&flags)) { - return OTS_FAILURE(); - } - we_have_instructions |= (flags & FLAG_WE_HAVE_INSTRUCTIONS) != 0; - size_t arg_size = 2; // glyph index - if (flags & FLAG_ARG_1_AND_2_ARE_WORDS) { - arg_size += 4; - } else { - arg_size += 2; - } - if (flags & FLAG_WE_HAVE_A_SCALE) { - arg_size += 2; - } else if (flags & FLAG_WE_HAVE_AN_X_AND_Y_SCALE) { - arg_size += 4; - } else if (flags & FLAG_WE_HAVE_A_TWO_BY_TWO) { - arg_size += 8; - } - if (!composite_stream->Skip(arg_size)) { - return OTS_FAILURE(); - } - } - size_t composite_glyph_size = composite_stream->offset() - start_offset; - if (composite_glyph_size + kCompositeGlyphBegin > dst_size) { - return OTS_FAILURE(); - } - Store16(dst, 0, 0xffff); // nContours = -1 for composite glyph - std::memcpy(dst + kCompositeGlyphBegin, - composite_stream->buffer() + start_offset, - composite_glyph_size); - *glyph_size = kCompositeGlyphBegin + composite_glyph_size; - *have_instructions = we_have_instructions; - return true; -} - -// Build TrueType loca table -bool StoreLoca(const std::vector<uint32_t>& loca_values, int index_format, - 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) { - return OTS_FAILURE(); - } - if (offset_size * loca_size > dst_size) { - return OTS_FAILURE(); - } - size_t offset = 0; - for (size_t i = 0; i < loca_values.size(); ++i) { - uint32_t value = loca_values[i]; - if (index_format) { - offset = StoreU32(dst, offset, value); - } else { - offset = Store16(dst, offset, value >> 1); - } - } - return true; -} - -// 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) { - static const int kNumSubStreams = 7; - ots::Buffer file(data, data_size); - uint32_t version; - std::vector<std::pair<const uint8_t*, size_t> > substreams(kNumSubStreams); - - if (!file.ReadU32(&version)) { - return OTS_FAILURE(); - } - uint16_t num_glyphs; - uint16_t index_format; - if (!file.ReadU16(&num_glyphs) || - !file.ReadU16(&index_format)) { - return OTS_FAILURE(); - } - unsigned int offset = (2 + kNumSubStreams) * 4; - if (offset > data_size) { - return OTS_FAILURE(); - } - // Invariant from here on: data_size >= offset - for (int i = 0; i < kNumSubStreams; ++i) { - uint32_t substream_size; - if (!file.ReadU32(&substream_size)) { - return OTS_FAILURE(); - } - if (substream_size > data_size - offset) { - return OTS_FAILURE(); - } - substreams[i] = std::make_pair(data + offset, substream_size); - offset += substream_size; - } - ots::Buffer n_contour_stream(substreams[0].first, substreams[0].second); - ots::Buffer n_points_stream(substreams[1].first, substreams[1].second); - ots::Buffer flag_stream(substreams[2].first, substreams[2].second); - ots::Buffer glyph_stream(substreams[3].first, substreams[3].second); - ots::Buffer composite_stream(substreams[4].first, substreams[4].second); - ots::Buffer bbox_stream(substreams[5].first, substreams[5].second); - ots::Buffer instruction_stream(substreams[6].first, substreams[6].second); - - std::vector<uint32_t> loca_values(num_glyphs + 1); - std::vector<unsigned int> n_points_vec; - std::vector<Point> points; - uint32_t loca_offset = 0; - for (unsigned int i = 0; i < num_glyphs; ++i) { - size_t glyph_size = 0; - uint16_t n_contours = 0; - if (!n_contour_stream.ReadU16(&n_contours)) { - return OTS_FAILURE(); - } - uint8_t* glyf_dst = dst + loca_offset; - size_t glyf_dst_size = dst_size - loca_offset; - if (n_contours == 0xffff) { - // composite glyph - bool have_instructions = false; - unsigned int instruction_size = 0; - if (!ProcessComposite(&composite_stream, glyf_dst, glyf_dst_size, - &glyph_size, &have_instructions)) { - return OTS_FAILURE(); - } - if (have_instructions) { - if (!Read255UShort(&glyph_stream, &instruction_size)) { - return OTS_FAILURE(); - } - if (instruction_size + 2 > glyf_dst_size - glyph_size) { - return OTS_FAILURE(); - } - Store16(glyf_dst, glyph_size, instruction_size); - if (!instruction_stream.Read(glyf_dst + glyph_size + 2, - instruction_size)) { - return OTS_FAILURE(); - } - glyph_size += instruction_size + 2; - } - } else if (n_contours > 0) { - // simple glyph - n_points_vec.clear(); - points.clear(); - unsigned int total_n_points = 0; - unsigned int n_points_contour; - for (unsigned int j = 0; j < n_contours; ++j) { - if (!Read255UShort(&n_points_stream, &n_points_contour)) { - return OTS_FAILURE(); - } - n_points_vec.push_back(n_points_contour); - if (total_n_points + n_points_contour < total_n_points) { - return OTS_FAILURE(); - } - total_n_points += n_points_contour; - } - unsigned int flag_size = total_n_points; - if (flag_size > flag_stream.length() - flag_stream.offset()) { - return OTS_FAILURE(); - } - const uint8_t* flags_buf = flag_stream.buffer() + flag_stream.offset(); - const uint8_t* triplet_buf = glyph_stream.buffer() + - glyph_stream.offset(); - size_t triplet_size = glyph_stream.length() - glyph_stream.offset(); - size_t triplet_bytes_consumed = 0; - if (!TripletDecode(flags_buf, triplet_buf, triplet_size, total_n_points, - &points, &triplet_bytes_consumed)) { - return OTS_FAILURE(); - } - const uint32_t header_and_endpts_contours_size = - kEndPtsOfContoursOffset + 2 * n_contours; - if (glyf_dst_size < header_and_endpts_contours_size) { - return OTS_FAILURE(); - } - Store16(glyf_dst, 0, n_contours); - ComputeBbox(points, glyf_dst); - size_t offset = kEndPtsOfContoursOffset; - int end_point = -1; - for (unsigned int contour_ix = 0; contour_ix < n_contours; ++contour_ix) { - end_point += n_points_vec[contour_ix]; - if (end_point >= 65536) { - return OTS_FAILURE(); - } - offset = Store16(glyf_dst, offset, end_point); - } - if (!flag_stream.Skip(flag_size)) { - return OTS_FAILURE(); - } - if (!glyph_stream.Skip(triplet_bytes_consumed)) { - return OTS_FAILURE(); - } - unsigned int instruction_size; - if (!Read255UShort(&glyph_stream, &instruction_size)) { - return OTS_FAILURE(); - } - if (glyf_dst_size - header_and_endpts_contours_size < - instruction_size + 2) { - return OTS_FAILURE(); - } - uint8_t* instruction_dst = glyf_dst + header_and_endpts_contours_size; - Store16(instruction_dst, 0, instruction_size); - if (!instruction_stream.Read(instruction_dst + 2, instruction_size)) { - return OTS_FAILURE(); - } - if (!StorePoints(points, n_contours, instruction_size, - glyf_dst, glyf_dst_size, &glyph_size)) { - return OTS_FAILURE(); - } - } else { - glyph_size = 0; - } - loca_values[i] = loca_offset; - if (glyph_size + 3 < glyph_size) { - return OTS_FAILURE(); - } - glyph_size = Round4(glyph_size); - if (glyph_size > dst_size - loca_offset) { - // This shouldn't happen, but this test defensively maintains the - // invariant that loca_offset <= dst_size. - return OTS_FAILURE(); - } - loca_offset += glyph_size; - } - loca_values[num_glyphs] = loca_offset; - if (!ProcessBboxStream(&bbox_stream, num_glyphs, loca_values, - dst, dst_size)) { - return OTS_FAILURE(); - } - return StoreLoca(loca_values, index_format, loca_buf, loca_size); -} - -// This is linear search, but could be changed to binary because we -// do have a guarantee that the tables are sorted by tag. But the total -// cpu time is expected to be very small in any case. -const Table* FindTable(const std::vector<Table>& tables, uint32_t tag) { - size_t n_tables = tables.size(); - for (size_t i = 0; i < n_tables; ++i) { - if (tables[i].tag == tag) { - return &tables[i]; - } - } - return NULL; -} - -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 == TAG('g', 'l', 'y', 'f')) { - const Table* glyf_table = FindTable(tables, tag); - const Table* loca_table = FindTable(tables, TAG('l', 'o', 'c', 'a')); - if (glyf_table == NULL || loca_table == NULL) { - return OTS_FAILURE(); - } - if (static_cast<uint64_t>(glyf_table->dst_offset + glyf_table->dst_length) > - dst_length) { - return OTS_FAILURE(); - } - if (static_cast<uint64_t>(loca_table->dst_offset + loca_table->dst_length) > - dst_length) { - return OTS_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); - } else if (tag == TAG('l', 'o', 'c', 'a')) { - // processing was already done by glyf table, but validate - if (!FindTable(tables, TAG('g', 'l', 'y', 'f'))) { - return OTS_FAILURE(); - } - } else { - // transform for the tag is not known - return OTS_FAILURE(); - } - 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]; - } - return checksum; -} - -bool FixChecksums(const std::vector<Table>& tables, uint8_t* dst) { - const Table* head_table = FindTable(tables, TAG('h', 'e', 'a', 'd')); - if (head_table == NULL || - head_table->dst_length < kCheckSumAdjustmentOffset + 4) { - return OTS_FAILURE(); - } - size_t adjustment_offset = head_table->dst_offset + kCheckSumAdjustmentOffset; - StoreU32(dst, adjustment_offset, 0); - 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); - StoreU32(dst, kSfntHeaderSize + i * kSfntEntrySize + 4, checksum); - file_checksum += checksum; - } - file_checksum += ComputeChecksum(dst, - kSfntHeaderSize + kSfntEntrySize * n_tables); - uint32_t checksum_adjustment = 0xb1b0afba - file_checksum; - StoreU32(dst, adjustment_offset, checksum_adjustment); - return true; -} - -bool Woff2Uncompress(uint8_t* dst_buf, size_t dst_size, - const uint8_t* src_buf, size_t src_size, uint32_t compression_type) { - if (compression_type == kCompressionTypeGzip) { - uLongf uncompressed_length = dst_size; - int r = uncompress(reinterpret_cast<Bytef *>(dst_buf), &uncompressed_length, - src_buf, src_size); - if (r != Z_OK || uncompressed_length != dst_size) { - return OTS_FAILURE(); - } - return true; -#ifdef USE_LZMA - } else if (compression_type == kCompressionTypeLzma) { - if (src_size < kLzmaHeaderSize) { - // Make sure we have at least a full Lzma header - return OTS_FAILURE(); - } - // TODO: check that size matches (or elide size?) - size_t uncompressed_size = dst_size; - size_t compressed_size = src_size; - int result = LzmaUncompress(dst_buf, &dst_size, - src_buf + kLzmaHeaderSize, &compressed_size, - src_buf, LZMA_PROPS_SIZE); - if (result != SZ_OK || uncompressed_size != dst_size) { - return OTS_FAILURE(); - } - return true; -#endif - } - // Unknown compression type - return OTS_FAILURE(); -} - -bool ReadLongDirectory(ots::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 OTS_FAILURE(); - } - } - return true; -} - -const uint32_t known_tags[29] = { - TAG('c', 'm', 'a', 'p'), // 0 - TAG('h', 'e', 'a', 'd'), // 1 - TAG('h', 'h', 'e', 'a'), // 2 - TAG('h', 'm', 't', 'x'), // 3 - TAG('m', 'a', 'x', 'p'), // 4 - TAG('n', 'a', 'm', 'e'), // 5 - TAG('O', 'S', '/', '2'), // 6 - TAG('p', 'o', 's', 't'), // 7 - TAG('c', 'v', 't', ' '), // 8 - TAG('f', 'p', 'g', 'm'), // 9 - TAG('g', 'l', 'y', 'f'), // 10 - TAG('l', 'o', 'c', 'a'), // 11 - TAG('p', 'r', 'e', 'p'), // 12 - TAG('C', 'F', 'F', ' '), // 13 - TAG('V', 'O', 'R', 'G'), // 14 - TAG('E', 'B', 'D', 'T'), // 15 - TAG('E', 'B', 'L', 'C'), // 16 - TAG('g', 'a', 's', 'p'), // 17 - TAG('h', 'd', 'm', 'x'), // 18 - TAG('k', 'e', 'r', 'n'), // 19 - TAG('L', 'T', 'S', 'H'), // 20 - TAG('P', 'C', 'L', 'T'), // 21 - TAG('V', 'D', 'M', 'X'), // 22 - TAG('v', 'h', 'e', 'a'), // 23 - TAG('v', 'm', 't', 'x'), // 24 - TAG('B', 'A', 'S', 'E'), // 25 - TAG('G', 'D', 'E', 'F'), // 26 - TAG('G', 'P', 'O', 'S'), // 27 - TAG('G', 'S', 'U', 'B'), // 28 -}; - -bool ReadShortDirectory(ots::Buffer* file, std::vector<Table>* tables, - size_t num_tables) { - uint32_t last_compression_type = 0; - for (size_t i = 0; i < num_tables; ++i) { - Table* table = &(*tables)[i]; - uint8_t flag_byte; - if (!file->ReadU8(&flag_byte)) { - return OTS_FAILURE(); - } - uint32_t tag; - if ((flag_byte & 0x1f) == 0x1f) { - if (!file->ReadU32(&tag)) { - return OTS_FAILURE(); - } - } else { - if ((flag_byte & 0x1f) >= (sizeof(known_tags) / sizeof(known_tags[0]))) { - return OTS_FAILURE(); - } - tag = known_tags[flag_byte & 0x1f]; - } - uint32_t flags = flag_byte >> 6; - if (flags == kShortFlagsContinue) { - flags = last_compression_type | kWoff2FlagsContinueStream; - } else { - if (flags == kCompressionTypeNone || - flags == kCompressionTypeGzip || - flags == kCompressionTypeLzma) { - last_compression_type = flags; - } else { - return OTS_FAILURE(); - } - } - if ((flag_byte & 0x20) != 0) { - flags |= kWoff2FlagsTransform; - } - uint32_t dst_length; - if (!ReadBase128(file, &dst_length)) { - return OTS_FAILURE(); - } - uint32_t transform_length = dst_length; - if ((flags & kWoff2FlagsTransform) != 0) { - if (!ReadBase128(file, &transform_length)) { - return OTS_FAILURE(); - } - } - uint32_t src_length = transform_length; - if ((flag_byte >> 6) == 1 || (flag_byte >> 6) == 2) { - if (!ReadBase128(file, &src_length)) { - return OTS_FAILURE(); - } - } - table->tag = tag; - table->flags = flags; - table->src_length = src_length; - table->transform_length = transform_length; - table->dst_length = dst_length; - } - return true; -} - -} // namespace - -namespace ots { - -size_t ComputeWOFF2FinalSize(const uint8_t* data, size_t length) { - ots::Buffer file(data, length); - uint32_t total_length; - - if (!file.Skip(16) || - !file.ReadU32(&total_length)) { - return 0; - } - return total_length; -} - -bool ConvertWOFF2ToTTF(uint8_t* result, size_t result_length, - const uint8_t* data, size_t length) { - static const uint32_t kWoff2Signature = 0x774f4632; // "wOF2" - ots::Buffer file(data, length); - - uint32_t signature; - uint32_t flavor; - if (!file.ReadU32(&signature) || signature != kWoff2Signature || - !file.ReadU32(&flavor)) { - return OTS_FAILURE(); - } - - // TODO(bashi): Should call IsValidVersionTag() here. - - uint32_t reported_length; - if (!file.ReadU32(&reported_length) || length != reported_length) { - return OTS_FAILURE(); - } - uint16_t num_tables; - if (!file.ReadU16(&num_tables) || !num_tables) { - return OTS_FAILURE(); - } - // We don't care about these fields of the header: - // uint16_t reserved - // uint32_t total_sfnt_size - // uint16_t major_version, minor_version - // uint32_t meta_offset, meta_length, meta_orig_length - // uint32_t priv_offset, priv_length - if (!file.Skip(30)) { - return OTS_FAILURE(); - } - std::vector<Table> tables(num_tables); - // Note: change below to ReadLongDirectory to enable long format. - if (!ReadShortDirectory(&file, &tables, num_tables)) { - return OTS_FAILURE(); - } - uint64_t src_offset = file.offset(); - uint64_t dst_offset = kSfntHeaderSize + - kSfntEntrySize * static_cast<uint64_t>(num_tables); - uint64_t uncompressed_sum = 0; - for (uint16_t i = 0; i < num_tables; ++i) { - Table* table = &tables[i]; - table->src_offset = src_offset; - src_offset += table->src_length; - if (src_offset > std::numeric_limits<uint32_t>::max()) { - return OTS_FAILURE(); - } - src_offset = Round4(src_offset); // TODO: reconsider - table->dst_offset = dst_offset; - dst_offset += table->dst_length; - if (dst_offset > std::numeric_limits<uint32_t>::max()) { - return OTS_FAILURE(); - } - dst_offset = Round4(dst_offset); - if ((table->flags & kCompressionTypeMask) != kCompressionTypeNone) { - uncompressed_sum += table->src_length; - if (uncompressed_sum > std::numeric_limits<uint32_t>::max()) { - return OTS_FAILURE(); - } - } - } - // Enforce same 30M limit on uncompressed tables as OTS - if (uncompressed_sum > 30 * 1024 * 1024) { - return OTS_FAILURE(); - } - if (src_offset > length || dst_offset > result_length) { - return OTS_FAILURE(); - } - - const uint32_t sfnt_header_and_table_directory_size = 12 + 16 * num_tables; - if (sfnt_header_and_table_directory_size > result_length) { - return OTS_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); - for (uint16_t i = 0; i < num_tables; ++i) { - const Table* table = &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); - } - std::vector<uint8_t> uncompressed_buf; - bool continue_valid = false; - for (uint16_t i = 0; i < num_tables; ++i) { - const Table* table = &tables[i]; - uint32_t flags = table->flags; - const uint8_t* src_buf = data + table->src_offset; - uint32_t compression_type = flags & kCompressionTypeMask; - const uint8_t* transform_buf = NULL; - size_t transform_length = table->transform_length; - if ((flags & kWoff2FlagsContinueStream) != 0) { - if (!continue_valid) { - return OTS_FAILURE(); - } - } else if (compression_type == kCompressionTypeNone) { - if (transform_length != table->src_length) { - return OTS_FAILURE(); - } - transform_buf = src_buf; - continue_valid = false; - } else if ((flags & kWoff2FlagsContinueStream) == 0) { - uint64_t total_size = transform_length; - for (uint16_t j = i + 1; j < num_tables; ++j) { - if ((tables[j].flags & kWoff2FlagsContinueStream) == 0) { - break; - } - total_size += tables[j].transform_length; - if (total_size > std::numeric_limits<uint32_t>::max()) { - return OTS_FAILURE(); - } - } - uncompressed_buf.resize(total_size); - if (!Woff2Uncompress(&uncompressed_buf[0], total_size, - src_buf, table->src_length, compression_type)) { - return OTS_FAILURE(); - } - transform_buf = &uncompressed_buf[0]; - continue_valid = true; - } else { - return OTS_FAILURE(); - } - - if ((flags & kWoff2FlagsTransform) == 0) { - if (transform_length != table->dst_length) { - return OTS_FAILURE(); - } - if (static_cast<uint64_t>(table->dst_offset + transform_length) > - result_length) { - return OTS_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 OTS_FAILURE(); - } - } - if (continue_valid) { - transform_buf += transform_length; - if (transform_buf > &uncompressed_buf[uncompressed_buf.size()]) { - return OTS_FAILURE(); - } - } - } - - return FixChecksums(tables, result); -} - -} // namespace ots diff --git a/cpp/woff2.gyp b/cpp/woff2.gyp deleted file mode 100644 index 6fd3dc9..0000000 --- a/cpp/woff2.gyp +++ /dev/null @@ -1,43 +0,0 @@ -{ - 'variables': { - 'ots_include_dirs': [ - # This isn't particularly elegant, but it works - '../ots-read-only/include', - '../ots-read-only/src', - ], - }, - 'target_defaults': { - 'defines': [ - 'OTS_DEBUG', - ], - }, - 'targets': [ - { - 'target_name': 'woff2', - 'type': 'static_library', - 'sources': [ - 'woff2.cc', - ], - 'include_dirs': [ - '<@(ots_include_dirs)', - ], - 'dependencies': [ - '../ots-read-only/ots-standalone.gyp:ots', - ], - }, - { - 'target_name': 'woff2-decompress', - 'type': 'executable', - 'sources': [ - 'woff2-decompress.cc', - ], - 'include_dirs': [ - '<@(ots_include_dirs)', - ], - 'dependencies': [ - 'woff2', - ], - }, - ], -} - diff --git a/cpp/woff2.h b/cpp/woff2.h deleted file mode 100644 index 64ae6df..0000000 --- a/cpp/woff2.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2012 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef OTS_WOFF2_H_ -#define OTS_WOFF2_H_ - -namespace ots { - -// Compute the size of the final uncompressed font, or 0 on error. -size_t ComputeWOFF2FinalSize(const uint8_t *data, size_t length); - -// Decompresses the font into the target buffer. The result_length should -// be the same as determined by ComputeFinalSize(). Returns true on successful -// decompression. -bool ConvertWOFF2ToTTF(uint8_t *result, size_t result_length, - const uint8_t *data, size_t length); - -} - -#endif // OTS_WOFF2_H_ diff --git a/lib/eotconverter.jar b/lib/eotconverter.jar Binary files differdeleted file mode 100644 index 6543d70..0000000 --- a/lib/eotconverter.jar +++ /dev/null diff --git a/lib/guava-11.0.1.jar b/lib/guava-11.0.1.jar Binary files differdeleted file mode 100644 index af4a383..0000000 --- a/lib/guava-11.0.1.jar +++ /dev/null diff --git a/lib/lzma.jar b/lib/lzma.jar Binary files differdeleted file mode 100644 index 29a44e3..0000000 --- a/lib/lzma.jar +++ /dev/null diff --git a/lib/sfntly.jar b/lib/sfntly.jar Binary files differdeleted file mode 100644 index 415d5c5..0000000 --- a/lib/sfntly.jar +++ /dev/null diff --git a/lib/woffconverter.jar b/lib/woffconverter.jar Binary files differdeleted file mode 100644 index 1ac0133..0000000 --- a/lib/woffconverter.jar +++ /dev/null diff --git a/src/com/google/typography/font/compression/AdvWidth.java b/src/com/google/typography/font/compression/AdvWidth.java deleted file mode 100644 index 5bf67b5..0000000 --- a/src/com/google/typography/font/compression/AdvWidth.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2011 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.google.typography.font.compression; - -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.Tag; -import com.google.typography.font.sfntly.data.WritableFontData; -import com.google.typography.font.sfntly.table.core.HorizontalMetricsTable; - -/** - * Extract just advance widths from hmtx table. - * - * @author Raph Levien - */ -public class AdvWidth { - - public static WritableFontData encode(Font font) { - HorizontalMetricsTable hmtx = font.getTable(Tag.hmtx); - int nMetrics = hmtx.numberOfHMetrics(); - WritableFontData result = WritableFontData.createWritableFontData(nMetrics * 2); - for (int i = 0; i < nMetrics; i++) { - result.writeShort(i * 2, hmtx.hMetricAdvanceWidth(i)); - } - return result; - } -} diff --git a/src/com/google/typography/font/compression/CmapEncoder.java b/src/com/google/typography/font/compression/CmapEncoder.java deleted file mode 100644 index 64888d6..0000000 --- a/src/com/google/typography/font/compression/CmapEncoder.java +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2011 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.google.typography.font.compression; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.Tag; -import com.google.typography.font.sfntly.table.core.CMap; -import com.google.typography.font.sfntly.table.core.CMapTable; -import com.google.typography.font.sfntly.table.core.MaximumProfileTable; - -import java.io.ByteArrayOutputStream; -import java.util.List; -import java.util.Map; - -/** - * Experimental CMap encoder, based primarily on writing the _inverse_ encoding. - * - * @author Raph Levien - */ -public class CmapEncoder { - - public static byte[] encode(Font font) { - int nGlyphs = font.<MaximumProfileTable>getTable(Tag.maxp).numGlyphs(); - CMapTable cmapTable = font.getTable(Tag.cmap); - CMap cmap = getBestCMap(cmapTable); - Map<Integer, Integer> invEncoding = Maps.newHashMap(); - List<Integer> exceptions = Lists.newArrayList(); - for (Integer i : cmap) { - int glyphId = cmap.glyphId(i); - if (invEncoding.containsKey(glyphId)) { - exceptions.add(i); - exceptions.add(glyphId); - } else { - invEncoding.put(glyphId, i); - } - } - ByteArrayOutputStream os = new ByteArrayOutputStream(); - int last = -1; - for (int i = 0; i < nGlyphs; i++) { - if (invEncoding.containsKey(i)) { - int value = invEncoding.get(i); - int delta = value - last; - writeVShort(os, delta); - last = value; - } else { - writeVShort(os, 0); - } - } - for (int i : exceptions) { - writeVShort(os, i); - } - return os.toByteArray(); - } - - private static CMap getBestCMap(CMapTable cmapTable) { - for (CMap cmap : cmapTable) { - if (cmap.format() == CMap.CMapFormat.Format12.value()) { - return cmap; - } - } - for (CMap cmap : cmapTable) { - if (cmap.format() == CMap.CMapFormat.Format4.value()) { - return cmap; - } - } - return null; - } - - // A simple signed varint encoding - static void writeVShort(ByteArrayOutputStream os, int value) { - if (value >= 0x2000 || value < -0x2000) { - os.write((byte)(0x80 | ((value >> 14) & 0x7f))); - } - if (value >= 0x40 || value < -0x40) { - os.write((byte)(0x80 | ((value >> 7) & 0x7f))); - } - os.write((byte)(value & 0x7f)); - } -} diff --git a/src/com/google/typography/font/compression/CompressLzma.java b/src/com/google/typography/font/compression/CompressLzma.java deleted file mode 100644 index 4bda54c..0000000 --- a/src/com/google/typography/font/compression/CompressLzma.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. - -package com.google.typography.font.compression; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import SevenZip.Compression.LZMA.Encoder; - -/** - * @author Raph Levien - * @author David Kuettel - */ -public class CompressLzma { - - public static byte[] compress(byte[] input) { - try { - ByteArrayInputStream in = new ByteArrayInputStream(input); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - Encoder encoder = new Encoder(); - encoder.SetAlgorithm(2); - encoder.SetDictionarySize(1 << 23); - encoder.SetNumFastBytes(128); - encoder.SetMatchFinder(1); - encoder.SetLcLpPb(3, 0, 2); - encoder.SetEndMarkerMode(true); - encoder.WriteCoderProperties(out); - for (int i = 0; i < 8; i++) { - out.write((int) ((long) -1 >>> (8 * i)) & 0xFF); - } - encoder.Code(in, out, -1, -1, null); - - out.flush(); - out.close(); - - return out.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/com/google/typography/font/compression/CompressionRunner.java b/src/com/google/typography/font/compression/CompressionRunner.java deleted file mode 100644 index 75d6370..0000000 --- a/src/com/google/typography/font/compression/CompressionRunner.java +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2011 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.google.typography.font.compression; - -import com.google.common.collect.Lists; -import com.google.common.io.Files; -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.FontFactory; - -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.List; - -/** - * A command-line tool for running different experimental compression code over - * a corpus of fonts, and gathering statistics, particularly compression - * efficiency. - * - * This is not intended to be production code. - * - * @author Raph Levien - */ -public class CompressionRunner { - - public static void main(String[] args) throws IOException { - boolean generateOutput = false; - List<String> descs = Lists.newArrayList(); - String baseline = "gzip"; - - List<String> filenames = Lists.newArrayList(); - for (int i = 0; i < args.length; i++) { - if (args[i].charAt(0) == '-') { - if (args[i].equals("-o")) { - generateOutput = true; - } else if (args[i].equals("-x")) { - descs.add(args[i + 1]); - i++; - } else if (args[i].equals("-b")) { - baseline = args[i + 1]; - i++; - } - } else { - filenames.add(args[i]); - } - } - - // String baseline = "glyf/triplet,code,push:lzma"; - // String baseline = "glyf/cbbox,triplet,code,push:hdmx:lzma"; - // descs.add("woff2"); - if (descs.isEmpty()) { - descs.add("glyf/cbbox,triplet,code,reslice:woff2/lzma"); - } - run(filenames, baseline, descs, generateOutput); - } - - private static void run(List<String> filenames, String baseline, List<String> descs, - boolean generateOutput) throws IOException { - PrintWriter o = new PrintWriter(System.out); - List<StatsCollector> stats = Lists.newArrayList(); - for (int i = 0; i < descs.size(); i++) { - stats.add(new StatsCollector()); - } - FontFactory fontFactory = FontFactory.getInstance(); - o.println("<html>"); - for (String filename : filenames) { - byte[] bytes = Files.toByteArray(new File(filename)); - Font font = fontFactory.loadFonts(bytes)[0]; - byte[] baselineResult = Experiment.run(font, baseline); - o.printf("<!-- %s: baseline %d bytes", new File(filename).getName(), baselineResult.length); - for (int i = 0; i < descs.size(); i++) { - byte[] expResult = Experiment.run(font, descs.get(i)); - if (generateOutput) { - String newFilename = filename; - if (newFilename.endsWith(".ttf")) { - newFilename = newFilename.substring(0, newFilename.length() - 4); - } - newFilename += ".woff2"; - Files.write(expResult, new File(newFilename)); - } - double percent = 100. * expResult.length / baselineResult.length; - stats.get(i).addStat(percent); - o.printf(", %c %.2f%%", 'A' + i, percent); - } - o.printf(" -->\n"); - } - stats.get(0).chartHeader(o, descs.size()); - for (int i = 0; i < descs.size(); i++) { - stats.get(i).chartData(o, i + 1); - } - stats.get(0).chartEnd(o); - o.printf("<p>baseline: %s</p>\n", baseline); - for (int i = 0; i < descs.size(); i++) { - StatsCollector sc = stats.get(i); - o.printf("<p>%c: %s: median %f, mean %f</p>\n", - 'A' + i, descs.get(i), sc.median(), sc.mean()); - } - stats.get(0).chartFooter(o); - o.close(); - } -} diff --git a/src/com/google/typography/font/compression/CompressionStats.java b/src/com/google/typography/font/compression/CompressionStats.java deleted file mode 100644 index 9b5caee..0000000 --- a/src/com/google/typography/font/compression/CompressionStats.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.google.typography.font.compression; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Compression stats, both aggregate and per font. - * - * @author David Kuettel - */ -public class CompressionStats { - - public enum Size { ORIGINAL, GZIP, WOFF, WOFF2 } - - private final List<Stats> values = Lists.newArrayList(); - - public void add(Stats stat) { - values.add(stat); - } - - public List<Stats> values() { - return values; - } - - public double mean(Size size) { - double sum = 0; - for (Long value : values(size)) { - sum += value; - } - return sum / values.size(); - } - - public double median(Size size) { - List<Long> list = values(size); - Collections.sort(list); - int length = list.size(); - if (length % 2 == 1) { - return list.get((length - 1) / 2); - } else { - return 0.5 * (list.get(length / 2 - 1) + list.get(length / 2)); - } - } - - private List<Long> values(Size size) { - List<Long> list = Lists.newArrayList(); - for (Stats stats : values) { - list.add(stats.getSize(size)); - } - return list; - } - - public static class Stats { - - private final String filename; - private final Map<Size, Long> sizes; - - private Stats(String filename, Map<Size, Long> sizes) { - this.filename = filename; - this.sizes = sizes; - } - - public String getFilename() { - return filename; - } - - public long getSize(Size size) { - return sizes.get(size); - } - - public double getPercent(Size s1, Size s2) { - long v1 = sizes.get(s1); - long v2 = sizes.get(s2); - return 100.0 * (v1 - v2) / v1; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private String filename; - private Map<Size, Long> sizes = Maps.newHashMap(); - - public Builder setFilename(String filename) { - this.filename = filename; - return this; - } - - public Builder setSize(Size key, long value) { - this.sizes.put(key, value); - return this; - } - - public Stats build() { - return new Stats(filename, ImmutableMap.copyOf(sizes)); - } - } - } -} diff --git a/src/com/google/typography/font/compression/CsvReport.java b/src/com/google/typography/font/compression/CsvReport.java deleted file mode 100644 index 4125a23..0000000 --- a/src/com/google/typography/font/compression/CsvReport.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.google.typography.font.compression; - -import com.google.common.io.Closeables; - -import java.io.BufferedWriter; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; - -/** - * Generates a CSV report containing the compression stats. - * - * @author David Kuettel - */ -public class CsvReport { - - public static void create(CompressionStats stats, String filename) throws IOException { - PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename))); - try { - writer.printf("Font, Original (bytes), WOFF 1.0 (bytes), WOFF 2.0 (bytes), %% Improvement\n"); - for (CompressionStats.Stats stat : stats.values()) { - writer.printf("%s, %d, %d, %d, %.2f%%\n", - stat.getFilename(), - stat.getSize(CompressionStats.Size.ORIGINAL), - stat.getSize(CompressionStats.Size.WOFF), - stat.getSize(CompressionStats.Size.WOFF2), - stat.getPercent(CompressionStats.Size.WOFF, CompressionStats.Size.WOFF2)); - } - } finally { - Closeables.closeQuietly(writer); - } - } -} diff --git a/src/com/google/typography/font/compression/Experiment.java b/src/com/google/typography/font/compression/Experiment.java deleted file mode 100644 index a5e97e9..0000000 --- a/src/com/google/typography/font/compression/Experiment.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.google.typography.font.compression; - -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.FontFactory; -import com.google.typography.font.sfntly.Tag; -import com.google.typography.font.tools.conversion.eot.EOTWriter; -import com.google.typography.font.tools.conversion.woff.WoffWriter; - -import java.io.IOException; -import java.util.Map; -import java.util.Set; - -/** - * @author Raph Levien - */ -public class Experiment { - - /** - * Does one experimental compression on a font, using the string to guide what - * gets done. - * - * @param srcFont Source font - * @param desc experiment description string; the exact format is probably - * still evolving - * @return serialization of compressed font - * @throws IOException - */ - public static byte[] run(Font srcFont, String desc) throws IOException { - Font font = srcFont; - FontFactory fontFactory = FontFactory.getInstance(); - String[] pieces = desc.split(":"); - boolean keepDsig = false; - - for (int i = 0; i < pieces.length - 1; i++) { - String[] piece = pieces[i].split("/"); - String cmd = piece[0]; - if (cmd.equals("glyf")) { - font = FontUtil.preprocessMtxGlyf(font, piece.length > 1 ? piece[1] : "").build(); - } else if (cmd.equals("hmtx")) { - font = FontUtil.preprocessHmtx(font).build(); - } else if (cmd.equals("hdmx")) { - font = FontUtil.preprocessHdmx(font).build(); - } else if (cmd.equals("cmap")) { - font = FontUtil.preprocessCmap(font).build(); - } else if (cmd.equals("kern")) { - font = FontUtil.preprocessKern(font).build(); - } else if (cmd.equals("keepdsig")) { - keepDsig = true; - } else if (cmd.equals("strip")) { - Set<Integer> removeTags = Sets.newTreeSet(); - for (String tag : piece[1].split(",")) { - removeTags.add(Tag.intValue(tag.getBytes())); - } - font = FontUtil.stripTags(font, removeTags).build(); - } - } - if (!keepDsig) { - font = FontUtil.stripTags(font, ImmutableSet.of(Tag.DSIG)).build(); - } - - String last = pieces[pieces.length - 1]; - String[] lastPieces = last.split("/"); - String lastBase = lastPieces[0]; - String lastArgs = lastPieces.length > 1 ? lastPieces[1] : ""; - if (!lastBase.equals("woff2")) { - Set<Integer> tagsToStrip = Sets.newHashSet(); - for (Map.Entry<Integer, Integer> mapping : Woff2Writer.getTransformMap().entrySet()) { - if (font.hasTable(mapping.getValue())) { - tagsToStrip.add(mapping.getKey()); - } - } - font = FontUtil.stripTags(font, tagsToStrip).build(); - } - - byte[] result = null; - if (lastBase.equals("gzip")) { - result = GzipUtil.deflate(FontUtil.toBytes(fontFactory, font)); - } else if (lastBase.equals("lzma")) { - result = CompressLzma.compress(FontUtil.toBytes(fontFactory, font)); - } else if (lastBase.equals("woff")) { - result = FontUtil.toBytes(new WoffWriter().convert(font)); - } else if (lastBase.equals("woff2")) { - result = FontUtil.toBytes(new Woff2Writer(lastArgs).convert(font)); - } else if (lastBase.equals("eot")) { - result = FontUtil.toBytes(new EOTWriter(true).convert(font)); - } else if (lastBase.equals("uncomp")) { - result = FontUtil.toBytes(fontFactory, font); - } - return result; - } -} diff --git a/src/com/google/typography/font/compression/FontUtil.java b/src/com/google/typography/font/compression/FontUtil.java deleted file mode 100644 index 155a042..0000000 --- a/src/com/google/typography/font/compression/FontUtil.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.google.typography.font.compression; - -import com.google.common.collect.ImmutableSet; -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.FontFactory; -import com.google.typography.font.sfntly.Tag; -import com.google.typography.font.sfntly.data.ReadableFontData; -import com.google.typography.font.tools.conversion.eot.HdmxEncoder; - -import java.io.ByteArrayOutputStream; -import java.util.Arrays; -import java.util.Set; - -/** - * Font utility methods - * - * @author Raph Levien - */ -public class FontUtil { - - public static Font.Builder stripTags(Font srcFont, Set<Integer> removeTags) { - FontFactory fontFactory = FontFactory.getInstance(); - Font.Builder fontBuilder = fontFactory.newFontBuilder(); - - for (Integer tag : srcFont.tableMap().keySet()) { - if (!removeTags.contains(tag)) { - fontBuilder.newTableBuilder(tag, srcFont.getTable(tag).readFontData()); - } - } - return fontBuilder; - } - - public static Font.Builder preprocessMtxGlyf(Font srcFont, String options) { - Font.Builder fontBuilder = stripTags(srcFont, ImmutableSet.<Integer>of()); - GlyfEncoder glyfEncoder = new GlyfEncoder(options); - glyfEncoder.encode(srcFont); - addTableBytes(fontBuilder, Tag.intValue(new byte[]{'g', 'l', 'z', '1'}), - glyfEncoder.getGlyfBytes()); - addTableBytes(fontBuilder, Tag.intValue(new byte[] {'l', 'o', 'c', 'z'}), - glyfEncoder.getLocaBytes()); - if (!Arrays.asList(options.split(",")).contains("reslice")) { - addTableBytes(fontBuilder, Tag.intValue(new byte[] {'g', 'l', 'z', '2'}), - glyfEncoder.getCodeBytes()); - addTableBytes(fontBuilder, Tag.intValue(new byte[] {'g', 'l', 'z', '3'}), - glyfEncoder.getPushBytes()); - } - return fontBuilder; - } - - public static Font.Builder preprocessHmtx(Font srcFont) { - Font.Builder fontBuilder = stripTags(srcFont, ImmutableSet.of(Tag.hmtx)); - addTableBytes(fontBuilder, Tag.intValue(new byte[] {'h', 'm', 't', 'z'}), - toBytes(AdvWidth.encode(srcFont))); - return fontBuilder; - } - - public static Font.Builder preprocessHdmx(Font srcFont) { - Font.Builder fontBuilder = stripTags(srcFont, ImmutableSet.of(Tag.hdmx)); - if (srcFont.hasTable(Tag.hdmx)) { - addTableBytes(fontBuilder, Tag.hdmx, toBytes(new HdmxEncoder().encode(srcFont))); - } - return fontBuilder; - } - - public static Font.Builder preprocessCmap(Font srcFont) { - Font.Builder fontBuilder = stripTags(srcFont, ImmutableSet.of(Tag.cmap)); - addTableBytes(fontBuilder, Tag.intValue(new byte[] {'c', 'm', 'a', 'z'}), - CmapEncoder.encode(srcFont)); - return fontBuilder; - } - - public static Font.Builder preprocessKern(Font srcFont) { - Font.Builder fontBuilder = stripTags(srcFont, ImmutableSet.of(Tag.kern)); - if (srcFont.hasTable(Tag.kern)) { - addTableBytes(fontBuilder, Tag.intValue(new byte[] {'k', 'e', 'r', 'z'}), - toBytes(KernEncoder.encode(srcFont))); - } - return fontBuilder; - } - - public static void addTableBytes(Font.Builder fontBuilder, int tag, byte[] contents) { - fontBuilder.newTableBuilder(tag, ReadableFontData.createReadableFontData(contents)); - } - - public static byte[] toBytes(FontFactory fontFactory, Font font) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - fontFactory.serializeFont(font, baos); - return baos.toByteArray(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static byte[] toBytes(ReadableFontData rfd) { - byte[] result = new byte[rfd.length()]; - rfd.readBytes(0, result, 0, rfd.length()); - return result; - } -} diff --git a/src/com/google/typography/font/compression/GlyfEncoder.java b/src/com/google/typography/font/compression/GlyfEncoder.java deleted file mode 100644 index 1144ee1..0000000 --- a/src/com/google/typography/font/compression/GlyfEncoder.java +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright 2011 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.google.typography.font.compression; - -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.Tag; -import com.google.typography.font.sfntly.data.ReadableFontData; -import com.google.typography.font.sfntly.table.core.FontHeaderTable; -import com.google.typography.font.sfntly.table.truetype.CompositeGlyph; -import com.google.typography.font.sfntly.table.truetype.Glyph; -import com.google.typography.font.sfntly.table.truetype.GlyphTable; -import com.google.typography.font.sfntly.table.truetype.LocaTable; -import com.google.typography.font.sfntly.table.truetype.SimpleGlyph; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * Implementation of compression of CTF glyph data, as per sections 5.6-5.10 and 6 of the spec. - * This is a hacked-up version with a number of options, for experimenting. - * - * @author Raph Levien - */ -public class GlyfEncoder { - - private final ByteArrayOutputStream nContourStream; - private final ByteArrayOutputStream nPointsStream; - private final ByteArrayOutputStream flagBytesStream; - private final ByteArrayOutputStream compositeStream; - private final ByteArrayOutputStream bboxStream; - private final ByteArrayOutputStream glyfStream; - private final ByteArrayOutputStream pushStream; - private final ByteArrayOutputStream codeStream; - private final boolean sbbox; - private final boolean cbbox; - private final boolean code; - private final boolean triplet; - private final boolean doPush; - private final boolean doHop; - private final boolean push2byte; - private final boolean reslice; - - private int nGlyphs; - private byte[] bboxBitmap; - private FontHeaderTable.IndexToLocFormat indexFmt; - - public GlyfEncoder(String options) { - glyfStream = new ByteArrayOutputStream(); - pushStream = new ByteArrayOutputStream(); - codeStream = new ByteArrayOutputStream(); - nContourStream = new ByteArrayOutputStream(); - nPointsStream = new ByteArrayOutputStream(); - flagBytesStream = new ByteArrayOutputStream(); - compositeStream = new ByteArrayOutputStream(); - bboxStream = new ByteArrayOutputStream(); - boolean sbbox = false; - boolean cbbox = false; - boolean code = false; - boolean triplet = false; - boolean doPush = false; - boolean reslice = false; - boolean doHop = false; - boolean push2byte = false; - for (String option : options.split(",")) { - if (option.equals("sbbox")) { - sbbox = true; - } else if (option.equals("cbbox")) { - cbbox = true; - } else if (option.equals("code")) { - code = true; - } else if (option.equals("triplet")) { - triplet = true; - } else if (option.equals("push")) { - doPush = true; - } else if (option.equals("hop")) { - doHop = true; - } else if (option.equals("push2byte")) { - push2byte = true; - } else if (option.equals("reslice")) { - reslice = true; - } - } - this.sbbox = sbbox; - this.cbbox = cbbox; - this.code = code; - this.triplet = triplet; - this.doPush = doPush; - this.doHop = doHop; - this.push2byte = push2byte; - this.reslice = reslice; - } - - public void encode(Font sourceFont) { - FontHeaderTable head = sourceFont.getTable(Tag.head); - indexFmt = head.indexToLocFormat(); - LocaTable loca = sourceFont.getTable(Tag.loca); - nGlyphs = loca.numGlyphs(); - GlyphTable glyf = sourceFont.getTable(Tag.glyf); - bboxBitmap = new byte[((nGlyphs + 31) >> 5) << 2]; - - for (int glyphId = 0; glyphId < nGlyphs; glyphId++) { - int sourceOffset = loca.glyphOffset(glyphId); - int length = loca.glyphLength(glyphId); - Glyph glyph = glyf.glyph(sourceOffset, length); - writeGlyph(glyphId, glyph); - } - } - - private void writeGlyph(int glyphId, Glyph glyph) { - try { - if (glyph == null || glyph.dataLength() == 0) { - writeNContours(0); - } else if (glyph instanceof SimpleGlyph) { - writeSimpleGlyph(glyphId, (SimpleGlyph)glyph); - } else if (glyph instanceof CompositeGlyph) { - writeCompositeGlyph(glyphId, (CompositeGlyph)glyph); - } - } catch (IOException e) { - throw new RuntimeException("unexpected IOException writing glyph data", e); - } - } - - private void writeInstructions(Glyph glyph) throws IOException{ - if (doPush) { - splitPush(glyph); - } else { - int pushCount = 0; - int codeSize = glyph.instructionSize(); - if (!reslice) { - write255UShort(glyfStream, pushCount); - } - write255UShort(glyfStream, codeSize); - if (codeSize > 0) { - if (code) { - glyph.instructions().copyTo(codeStream); - } else { - glyph.instructions().copyTo(glyfStream); - } - } - } - } - - private void writeSimpleGlyph(int glyphId, SimpleGlyph glyph) throws IOException { - int numContours = glyph.numberOfContours(); - writeNContours(numContours); - if (sbbox) { - writeBbox(glyphId, glyph); - } - // TODO: check that bbox matches, write bbox if not - for (int i = 0; i < numContours; i++) { - if (reslice) { - write255UShort(nPointsStream, glyph.numberOfPoints(i)); - } else { - write255UShort(glyfStream, glyph.numberOfPoints(i) - (i == 0 ? 1 : 0)); - } - } - ByteArrayOutputStream os = new ByteArrayOutputStream(); - int lastX = 0; - int lastY = 0; - for (int i = 0; i < numContours; i++) { - int numPoints = glyph.numberOfPoints(i); - for (int j = 0; j < numPoints; j++) { - int x = glyph.xCoordinate(i, j); - int y = glyph.yCoordinate(i, j); - int dx = x - lastX; - int dy = y - lastY; - if (triplet) { - writeTriplet(os, glyph.onCurve(i, j), dx, dy); - } else { - writeVShort(os, dx * 2 + (glyph.onCurve(i, j) ? 1 : 0)); - writeVShort(os, dy); - } - lastX = x; - lastY = y; - } - } - os.writeTo(glyfStream); - if (numContours > 0) { - writeInstructions(glyph); - } - } - - private void writeCompositeGlyph(int glyphId, CompositeGlyph glyph) throws IOException { - boolean haveInstructions = false; - writeNContours(-1); - if (cbbox) { - writeBbox(glyphId, glyph); - } - ByteArrayOutputStream outStream = reslice ? compositeStream : glyfStream; - for (int i = 0; i < glyph.numGlyphs(); i++) { - int flags = glyph.flags(i); - writeUShort(outStream, flags); - haveInstructions = (flags & CompositeGlyph.FLAG_WE_HAVE_INSTRUCTIONS) != 0; - writeUShort(outStream, glyph.glyphIndex(i)); - if ((flags & CompositeGlyph.FLAG_ARG_1_AND_2_ARE_WORDS) == 0) { - outStream.write(glyph.argument1(i)); - outStream.write(glyph.argument2(i)); - } else { - writeUShort(outStream, glyph.argument1(i)); - writeUShort(outStream, glyph.argument2(i)); - } - if (glyph.transformationSize(i) != 0) { - try { - outStream.write(glyph.transformation(i)); - } catch (IOException e) { - } - } - } - if (haveInstructions) { - writeInstructions(glyph); - } - } - - private void writeNContours(int nContours) { - if (reslice) { - writeUShort(nContourStream, nContours); - } else { - writeUShort(nContours); - } - } - - private void writeBbox(int glyphId, Glyph glyph) { - if (reslice) { - bboxBitmap[glyphId >> 3] |= 0x80 >> (glyphId & 7); - } - ByteArrayOutputStream outStream = reslice ? bboxStream : glyfStream; - writeUShort(outStream, glyph.xMin()); - writeUShort(outStream, glyph.yMin()); - writeUShort(outStream, glyph.xMax()); - writeUShort(outStream, glyph.yMax()); - } - - private void writeUShort(ByteArrayOutputStream os, int value) { - os.write(value >> 8); - os.write(value & 255); - } - - private void writeUShort(int value) { - writeUShort(glyfStream, value); - } - - private void writeLong(OutputStream os, int value) throws IOException { - os.write((value >> 24) & 255); - os.write((value >> 16) & 255); - os.write((value >> 8) & 255); - os.write(value & 255); - } - - // As per 6.1.1 of spec - // visible for testing - static void write255UShort(ByteArrayOutputStream os, int value) { - if (value < 0) { - throw new IllegalArgumentException(); - } - if (value < 253) { - os.write((byte)value); - } else if (value < 506) { - os.write(255); - os.write((byte)(value - 253)); - } else if (value < 762) { - os.write(254); - os.write((byte)(value - 506)); - } else { - os.write(253); - os.write((byte)(value >> 8)); - os.write((byte)(value & 0xff)); - } - } - - // As per 6.1.1 of spec - // visible for testing - static void write255Short(OutputStream os, int value) throws IOException { - int absValue = Math.abs(value); - if (value < 0) { - // spec is unclear about whether words should be signed. This code is conservative, but we - // can test once the implementation is working. - os.write(250); - } - if (absValue < 250) { - os.write((byte)absValue); - } else if (absValue < 500) { - os.write(255); - os.write((byte)(absValue - 250)); - } else if (absValue < 756) { - os.write(254); - os.write((byte)(absValue - 500)); - } else { - os.write(253); - os.write((byte)(absValue >> 8)); - os.write((byte)(absValue & 0xff)); - } - } - - // A simple signed varint encoding - static void writeVShort(ByteArrayOutputStream os, int value) { - if (value >= 0x2000 || value < -0x2000) { - os.write((byte)(0x80 | ((value >> 14) & 0x7f))); - } - if (value >= 0x40 || value < -0x40) { - os.write((byte)(0x80 | ((value >> 7) & 0x7f))); - } - os.write((byte)(value & 0x7f)); - } - - // As in section 5.11 of the spec - // visible for testing - void writeTriplet(OutputStream os, boolean onCurve, int x, int y) throws IOException { - int absX = Math.abs(x); - int absY = Math.abs(y); - int onCurveBit = onCurve ? 0 : 128; - int xSignBit = (x < 0) ? 0 : 1; - int ySignBit = (y < 0) ? 0 : 1; - int xySignBits = xSignBit + 2 * ySignBit; - ByteArrayOutputStream flagStream = reslice ? flagBytesStream : glyfStream; - - if (x == 0 && absY < 1280) { - flagStream.write(onCurveBit + ((absY & 0xf00) >> 7) + ySignBit); - os.write(absY & 0xff); - } else if (y == 0 && absX < 1280) { - flagStream.write(onCurveBit + 10 + ((absX & 0xf00) >> 7) + xSignBit); - os.write(absX & 0xff); - } else if (absX < 65 && absY < 65) { - flagStream.write(onCurveBit + 20 + ((absX - 1) & 0x30) + (((absY - 1) & 0x30) >> 2) + - xySignBits); - os.write((((absX - 1) & 0xf) << 4) | ((absY - 1) & 0xf)); - } else if (absX < 769 && absY < 769) { - flagStream.write(onCurveBit + 84 + 12 * (((absX - 1) & 0x300) >> 8) + - (((absY - 1) & 0x300) >> 6) + xySignBits); - os.write((absX - 1) & 0xff); - os.write((absY - 1) & 0xff); - } else if (absX < 4096 && absY < 4096) { - flagStream.write(onCurveBit + 120 + xySignBits); - os.write(absX >> 4); - os.write(((absX & 0xf) << 4) | (absY >> 8)); - os.write(absY & 0xff); - } else { - flagStream.write(onCurveBit + 124 + xySignBits); - os.write(absX >> 8); - os.write(absX & 0xff); - os.write(absY >> 8); - os.write(absY & 0xff); - } - } - - /** - * Split the instructions into a push sequence and the remainder of the instructions. - * Writes both streams, and the counts to the glyfStream. - * - * @param glyph - */ - private void splitPush(Glyph glyph) throws IOException { - int instrSize = glyph.instructionSize(); - ReadableFontData data = glyph.instructions(); - int i = 0; - List<Integer> result = new ArrayList<Integer>(); - // All push sequences are at least two bytes, make sure there's enough room - while (i + 1 < instrSize) { - int ix = i; - int instr = data.readUByte(ix++); - int n = 0; - int size = 0; - if (instr == 0x40 || instr == 0x41) { - // NPUSHB, NPUSHW - n = data.readUByte(ix++); - size = (instr & 1) + 1; - } else if (instr >= 0xB0 && instr < 0xC0) { - // PUSHB, PUSHW - n = 1 + (instr & 7); - size = ((instr & 8) >> 3) + 1; - } else { - break; - } - if (i + size * n > instrSize) { - // This is a broken font, and a potential buffer overflow, but in the interest - // of preserving the original, we just put the broken instruction sequence in - // the stream. - break; - } - for (int j = 0; j < n; j++) { - if (size == 1) { - result.add(data.readUByte(ix)); - } else { - result.add(data.readShort(ix)); - } - ix += size; - } - i = ix; - } - int pushCount = result.size(); - int codeSize = instrSize - i; - write255UShort(glyfStream, pushCount); - write255UShort(glyfStream, codeSize); - encodePushSequence(pushStream, result); - if (codeSize > 0) { - data.slice(i).copyTo(codeStream); - } - } - - // As per section 6.2.2 of the spec - private void encodePushSequence(ByteArrayOutputStream os, List<Integer> data) throws IOException { - int n = data.size(); - int hopSkip = 0; - for (int i = 0; i < n; i++) { - if ((hopSkip & 1) == 0) { - int val = data.get(i); - if (doHop && hopSkip == 0 && i >= 2 && - i + 2 < n && val == data.get(i - 2) && val == data.get(i + 2)) { - if (i + 4 < n && val == data.get(i + 4)) { - // Hop4 code - os.write(252); - hopSkip = 0x14; - } else { - // Hop3 code - os.write(251); - hopSkip = 4; - } - } else { - if (push2byte) { - // Measure relative effectiveness of 255Short literal encoding vs 2-byte ushort. - writeUShort(os, data.get(i)); - } else { - write255Short(os, data.get(i)); - } - } - } - hopSkip >>= 1; - } - } - - public byte[] getGlyfBytes() { - if (reslice) { - ByteArrayOutputStream newStream = new ByteArrayOutputStream(); - try { - // Pack all the glyf streams in a sensible way - writeLong(newStream, 0); // version - writeUShort(newStream, nGlyphs); - writeUShort(newStream, indexFmt.value()); - writeLong(newStream, nContourStream.size()); - writeLong(newStream, nPointsStream.size()); - writeLong(newStream, flagBytesStream.size()); - writeLong(newStream, glyfStream.size()); - writeLong(newStream, compositeStream.size()); - writeLong(newStream, bboxBitmap.length + bboxStream.size()); - writeLong(newStream, codeStream.size()); -// System.out.printf("stream sizes = %d %d %d %d %d %d %d\n", -// nContourStream.size(), nPointsStream.size(), flagBytesStream.size(), glyfStream.size(), -// compositeStream.size(), bboxStream.size(), codeStream.size()); - nContourStream.writeTo(newStream); - nPointsStream.writeTo(newStream); - flagBytesStream.writeTo(newStream); - glyfStream.writeTo(newStream); - compositeStream.writeTo(newStream); - newStream.write(bboxBitmap); - bboxStream.writeTo(newStream); - codeStream.writeTo(newStream); - } catch (IOException e) { - throw new RuntimeException("Can't happen, world must have come to end", e); - } - return newStream.toByteArray(); - } else { - return glyfStream.toByteArray(); - } - } - - public byte[] getPushBytes() { - return pushStream.toByteArray(); - } - - public byte[] getCodeBytes() { - return codeStream.toByteArray(); - } - - public byte[] getLocaBytes() { - return new byte[]{ }; - } -} diff --git a/src/com/google/typography/font/compression/GzipUtil.java b/src/com/google/typography/font/compression/GzipUtil.java deleted file mode 100644 index 75fac1b..0000000 --- a/src/com/google/typography/font/compression/GzipUtil.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2011 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.google.typography.font.compression; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -/** - * Simple utility for GZIP compression - * - * @author Raph Levien - */ -public class GzipUtil { - - public static byte[] deflate(byte[] bytes) { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater()); - dos.write(bytes, 0, bytes.length); - dos.close(); - return baos.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} - diff --git a/src/com/google/typography/font/compression/KernEncoder.java b/src/com/google/typography/font/compression/KernEncoder.java deleted file mode 100644 index 7d94ccb..0000000 --- a/src/com/google/typography/font/compression/KernEncoder.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2011 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.google.typography.font.compression; - -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.Tag; -import com.google.typography.font.sfntly.data.ReadableFontData; -import com.google.typography.font.sfntly.data.WritableFontData; -import com.google.typography.font.sfntly.table.Table; - -/** - * Encoder for "kern" table. This probably won't go in the spec because an even more - * effective technique would be to do class kerning in the GDEF tables, but, even so, I wanted - * to capture the stats. - * - * @author Raph Levien - */ -public class KernEncoder { - - public static WritableFontData encode(Font font) { - Table kernTable = font.getTable(Tag.kern); - ReadableFontData data = kernTable.readFontData(); - WritableFontData newData = WritableFontData.createWritableFontData(data.size()); - data.copyTo(newData); - if (data.readUShort(0) == 0 && data.readUShort(4) == 0) { - int base = 18; - int nPairs = data.readUShort(10); - for (int i = 0; i < nPairs; i++) { - newData.writeUShort(base + i * 2, data.readUShort(base + i * 6)); - newData.writeUShort(base + nPairs * 2 + i * 2, data.readUShort(base + i * 6 + 2)); - newData.writeUShort(base + nPairs * 4 + i * 2, data.readUShort(base + i * 6 + 4)); - } - } - return newData; - } -} diff --git a/src/com/google/typography/font/compression/SimpleRunner.java b/src/com/google/typography/font/compression/SimpleRunner.java deleted file mode 100644 index 43d554e..0000000 --- a/src/com/google/typography/font/compression/SimpleRunner.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.google.typography.font.compression; - -import com.google.common.io.Files; -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.FontFactory; -import com.google.typography.font.sfntly.Tag; -import com.google.typography.font.sfntly.table.truetype.GlyphTable; -import com.google.typography.font.sfntly.table.truetype.LocaTable; - -import java.io.File; -import java.io.IOException; - -/** - * Simple WOFF 2.0 compression report runner. - * - * @author David Kuettel - */ -public class SimpleRunner { - - private static final FontFactory FONT_FACTORY = FontFactory.getInstance(); - - private static final String WOFF = "woff"; - private static final String WOFF2 = "woff2/lzma"; - - private static final String TRUETYPE = "glyf/cbbox,triplet,code,reslice"; - private static final String CFF = ""; - - private static final String REPORT = "report.csv"; - - public static void main(String[] args) throws IOException { - if (args.length == 0) { - usage(); - } - CompressionStats stats = new CompressionStats(); - - System.out.printf("Analyzing (%d) fonts\n", args.length); - run(stats, args); - - System.out.printf("Creating report: %s\n", REPORT); - CsvReport.create(stats, REPORT); - } - - private static void run(CompressionStats stats, String[] filenames) throws IOException { - for (String filename : filenames) { - try { - File file = new File(filename); - byte[] bytes = Files.toByteArray(file); - Font font = FONT_FACTORY.loadFonts(bytes)[0]; - - byte[] woff = Experiment.run(font, WOFF); - byte[] woff2 = Experiment.run(font, getOptions(font)); - - CompressionStats.Stats stat = CompressionStats.Stats.builder() - .setFilename(file.getName()) - .setSize(CompressionStats.Size.ORIGINAL, bytes.length) - .setSize(CompressionStats.Size.WOFF, woff.length) - .setSize(CompressionStats.Size.WOFF2, woff2.length) - .build(); - stats.add(stat); - - System.out.printf("> %s, %d, %d, %d, %.2f%%\n", - stat.getFilename(), - stat.getSize(CompressionStats.Size.ORIGINAL), - stat.getSize(CompressionStats.Size.WOFF), - stat.getSize(CompressionStats.Size.WOFF2), - stat.getPercent(CompressionStats.Size.WOFF, CompressionStats.Size.WOFF2)); - - String target = filename.replaceAll("[.](ttf|otf)", ".woff2"); - Files.write(woff2, new File(target)); - - } catch (Throwable t) { - System.err.printf("WARNING: failed to compress: %s\n", filename); - t.printStackTrace(); - } - } - } - - private static boolean isTrueType(Font font) { - LocaTable loca = font.getTable(Tag.loca); - GlyphTable glyf = font.getTable(Tag.glyf); - return (loca != null && glyf != null); - } - - private static String getOptions(Font font) { - return String.format("%s:%s", (isTrueType(font)) ? TRUETYPE : CFF, WOFF2); - } - - private static void usage() { - System.err.println("Usage: SimpleRunner font..."); - System.exit(-1); - } -} diff --git a/src/com/google/typography/font/compression/StatsCollector.java b/src/com/google/typography/font/compression/StatsCollector.java deleted file mode 100644 index b2fead3..0000000 --- a/src/com/google/typography/font/compression/StatsCollector.java +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2011 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.google.typography.font.compression; - -import com.google.common.collect.Lists; - -import java.io.PrintWriter; -import java.util.Collections; -import java.util.List; - -/** - * Class for gathering up stats, for summarizing and graphing. - * - * @author Raph Levien - */ -public class StatsCollector { - - private final List<Double> values; - - public StatsCollector() { - values = Lists.newArrayList(); - } - - public void addStat(double value) { - values.add(value); - } - - public double mean() { - double sum = 0; - for (Double value : values) { - sum += value; - } - return sum / values.size(); - } - - public double median() { - Collections.sort(values); - int length = values.size(); - if (length % 2 == 1) { - return values.get((length - 1) / 2); - } else { - return 0.5 * (values.get(length / 2 - 1) + values.get(length / 2)); - } - } - - // Need to print <html> before calling this method - public void chartHeader(PrintWriter o, int n) { - o.println("<head>"); - o.println("<script type='text/javascript' src='https://www.google.com/jsapi'></script>"); - o.println("<script type='text/javascript'>"); - o.println("google.load('visualization', '1', {packages:['corechart']});"); - o.println("google.setOnLoadCallback(drawChart);"); - o.println("function drawChart() {"); - o.println(" var data = new google.visualization.DataTable()"); - o.println(" data.addColumn('string', 'Font');"); - if (n == 1) { - o.println(" data.addColumn('number', 'Ratio');"); - } else { - for (int i = 0; i < n; i++) { - o.printf(" data.addColumn('number', 'Ratio %c');\n", 'A' + i); - } - } - o.printf(" data.addRows(%d);\n", values.size()); - } - - public void chartData(PrintWriter o, int ix) { - Collections.sort(values); - int length = values.size(); - for (int i = 0; i < length; i++) { - o.printf(" data.setValue(%d, %d, %f);\n", i, ix, values.get(i)); - } - } - - public void chartEnd(PrintWriter o) { - o.println(" var chart = new google.visualization.LineChart(document.getElementById(" - + "'chart_div'));"); - o.println(" chart.draw(data, {width:700, height:400, title: 'Compression ratio'});"); - o.println("}"); - o.println("</script>"); - o.println("</head>"); - - o.println(); - o.println("<body>"); - o.println("<div id='chart_div'></div>"); - // TODO: split so we can get content into the HTML - } - public void chartFooter(PrintWriter o) { - o.println("</body>"); - o.println("</html>"); - } -} diff --git a/src/com/google/typography/font/compression/Woff2Writer.java b/src/com/google/typography/font/compression/Woff2Writer.java deleted file mode 100644 index 905a3ce..0000000 --- a/src/com/google/typography/font/compression/Woff2Writer.java +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2012 Google Inc. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package com.google.typography.font.compression; - -import com.google.common.collect.ImmutableBiMap; -import com.google.common.collect.Lists; -import com.google.typography.font.sfntly.Font; -import com.google.typography.font.sfntly.Tag; -import com.google.typography.font.sfntly.data.WritableFontData; -import com.google.typography.font.sfntly.table.Table; -import com.google.typography.font.sfntly.table.core.FontHeaderTable; - -import java.util.List; -import java.util.TreeSet; - -/** - * @author Raph Levien - */ -public class Woff2Writer { - - private static final long SIGNATURE = 0x774f4632; - private static final int WOFF2_HEADER_SIZE = 44; - private static final int TABLE_ENTRY_SIZE = 5 * 4; - private static final int FLAG_CONTINUE_STREAM = 1 << 4; - private static final int FLAG_APPLY_TRANSFORM = 1 << 5; - - private final CompressionType compressionType; - private final boolean longForm; - - public Woff2Writer(String args) { - CompressionType compressionType = CompressionType.NONE; - boolean longForm = false; - for (String arg : args.split(",")) { - if ("lzma".equals(arg)) { - compressionType = CompressionType.LZMA; - } else if ("gzip".equals(arg)) { - compressionType = CompressionType.GZIP; - } else if ("short".equals(arg)) { - longForm = false; - } else if ("long".equals(arg)) { - longForm = true; - } - } - this.compressionType = compressionType; - this.longForm = longForm; - } - - private static ImmutableBiMap<Integer, Integer> TRANSFORM_MAP = ImmutableBiMap.of( - Tag.glyf, Tag.intValue(new byte[] {'g', 'l', 'z', '1'}), - Tag.loca, Tag.intValue(new byte[] {'l', 'o', 'c', 'z'}) - ); - - public static ImmutableBiMap<Integer, Integer> getTransformMap() { - return TRANSFORM_MAP; - } - - private static ImmutableBiMap<Integer, Integer> KNOWN_TABLES = - new ImmutableBiMap.Builder<Integer, Integer>() - .put(Tag.intValue(new byte[] {'c', 'm', 'a', 'p'}), 0) - .put(Tag.intValue(new byte[] {'h', 'e', 'a', 'd'}), 1) - .put(Tag.intValue(new byte[] {'h', 'h', 'e', 'a'}), 2) - .put(Tag.intValue(new byte[] {'h', 'm', 't', 'x'}), 3) - .put(Tag.intValue(new byte[] {'m', 'a', 'x', 'p'}), 4) - .put(Tag.intValue(new byte[] {'n', 'a', 'm', 'e'}), 5) - .put(Tag.intValue(new byte[] {'O', 'S', '/', '2'}), 6) - .put(Tag.intValue(new byte[] {'p', 'o', 's', 't'}), 7) - .put(Tag.intValue(new byte[] {'c', 'v', 't', ' '}), 8) - .put(Tag.intValue(new byte[] {'f', 'p', 'g', 'm'}), 9) - .put(Tag.intValue(new byte[] {'g', 'l', 'y', 'f'}), 10) - .put(Tag.intValue(new byte[] {'l', 'o', 'c', 'a'}), 11) - .put(Tag.intValue(new byte[] {'p', 'r', 'e', 'p'}), 12) - .put(Tag.intValue(new byte[] {'C', 'F', 'F', ' '}), 13) - .put(Tag.intValue(new byte[] {'V', 'O', 'R', 'G'}), 14) - .put(Tag.intValue(new byte[] {'E', 'B', 'D', 'T'}), 15) - .put(Tag.intValue(new byte[] {'E', 'B', 'L', 'C'}), 16) - .put(Tag.intValue(new byte[] {'g', 'a', 's', 'p'}), 17) - .put(Tag.intValue(new byte[] {'h', 'd', 'm', 'x'}), 18) - .put(Tag.intValue(new byte[] {'k', 'e', 'r', 'n'}), 19) - .put(Tag.intValue(new byte[] {'L', 'T', 'S', 'H'}), 20) - .put(Tag.intValue(new byte[] {'P', 'C', 'L', 'T'}), 21) - .put(Tag.intValue(new byte[] {'V', 'D', 'M', 'X'}), 22) - .put(Tag.intValue(new byte[] {'v', 'h', 'e', 'a'}), 23) - .put(Tag.intValue(new byte[] {'v', 'm', 't', 'x'}), 24) - .put(Tag.intValue(new byte[] {'B', 'A', 'S', 'E'}), 25) - .put(Tag.intValue(new byte[] {'G', 'D', 'E', 'F'}), 26) - .put(Tag.intValue(new byte[] {'G', 'P', 'O', 'S'}), 27) - .put(Tag.intValue(new byte[] {'G', 'S', 'U', 'B'}), 28) - .build(); - - public WritableFontData convert(Font font) { - List<TableDirectoryEntry> entries = createTableDirectoryEntries(font); - int size = computeCompressedFontSize(entries); - WritableFontData writableFontData = WritableFontData.createWritableFontData(size); - int index = 0; - FontHeaderTable head = font.getTable(Tag.head); - index += writeWoff2Header(writableFontData, entries, font.sfntVersion(), size, - head.fontRevision()); -// System.out.printf("Wrote header, index = %d\n", index); - index += writeDirectory(writableFontData, index, entries); -// System.out.printf("Wrote directory, index = %d\n", index); - index += writeTables(writableFontData, index, entries); -// System.out.printf("Wrote tables, index = %d\n", index); - return writableFontData; - } - - private List<TableDirectoryEntry> createTableDirectoryEntries(Font font) { - List<TableDirectoryEntry> entries = Lists.newArrayList(); - TreeSet<Integer> tags = new TreeSet<Integer>(font.tableMap().keySet()); - - for (int tag : tags) { - Table table = font.getTable(tag); - byte[] uncompressedBytes = bytesFromTable(table); - byte[] transformedBytes = null; - if (TRANSFORM_MAP.containsValue(tag)) { - // Don't store the intermediate transformed tables under the nonstandard tags. - continue; - } - if (TRANSFORM_MAP.containsKey(tag)) { - int transformedTag = TRANSFORM_MAP.get(tag); - Table transformedTable = font.getTable(transformedTag); - if (transformedTable != null) { - transformedBytes = bytesFromTable(transformedTable); - } - } - if (transformedBytes == null) { - entries.add(new TableDirectoryEntry(tag, uncompressedBytes, compressionType)); - } else { - entries.add(new TableDirectoryEntry(tag, uncompressedBytes, transformedBytes, - FLAG_APPLY_TRANSFORM, compressionType)); - } - } - return entries; - } - - private byte[] bytesFromTable(Table table) { - int length = table.dataLength(); - byte[] bytes = new byte[length]; - table.readFontData().readBytes(0, bytes, 0, length); - return bytes; - } - - private int writeWoff2Header(WritableFontData writableFontData, - List<TableDirectoryEntry> entries, - int flavor, - int length, - int version) { - int index = 0; - index += writableFontData.writeULong(index, SIGNATURE); - index += writableFontData.writeULong(index, flavor); - index += writableFontData.writeULong(index, length); - index += writableFontData.writeUShort(index, entries.size()); // numTables - index += writableFontData.writeUShort(index, 0); // reserved - int uncompressedFontSize = computeUncompressedSize(entries); - index += writableFontData.writeULong(index, uncompressedFontSize); - index += writableFontData.writeFixed(index, version); - index += writableFontData.writeULong(index, 0); // metaOffset - index += writableFontData.writeULong(index, 0); // metaLength - index += writableFontData.writeULong(index, 0); // metaOrigLength - index += writableFontData.writeULong(index, 0); // privOffset - index += writableFontData.writeULong(index, 0); // privLength - return index; - } - - private int writeDirectory(WritableFontData writableFontData, int offset, - List<TableDirectoryEntry> entries) { - int directorySize = computeDirectoryLength(entries); - for (TableDirectoryEntry entry : entries) { - offset += entry.writeEntry(writableFontData, offset); - } - return directorySize; - } - - private int writeTables(WritableFontData writableFontData, int offset, - List<TableDirectoryEntry> entries) { - int start = offset; - for (TableDirectoryEntry entry : entries) { - offset += entry.writeData(writableFontData, offset); - offset = align4(offset); - } - return offset - start; - } - - private int computeDirectoryLength(List<TableDirectoryEntry> entries) { - if (longForm) { - return TABLE_ENTRY_SIZE * entries.size(); - } else { - int size = 0; - for (TableDirectoryEntry entry : entries) { - size += entry.writeEntry(null, size); - } - return size; - } - } - - private int align4(int value) { - return (value + 3) & -4; - } - - private int computeUncompressedSize(List<TableDirectoryEntry> entries) { - int size = 20 + 16 * entries.size(); // sfnt header length - for (TableDirectoryEntry entry : entries) { - size += entry.getOrigLength(); - size = align4(size); - } - return size; - } - - private int computeCompressedFontSize(List<TableDirectoryEntry> entries) { - int fontSize = WOFF2_HEADER_SIZE; - fontSize += computeDirectoryLength(entries); - for (TableDirectoryEntry entry : entries) { - fontSize += entry.getCompLength(); - fontSize = align4(fontSize); - } - return fontSize; - } - - private enum CompressionType { - NONE, GZIP, LZMA - } - - private static long flagsForCompression(CompressionType compressionType) { - switch (compressionType) { - case NONE: - return 0; - case GZIP: - return 1; - case LZMA: - return 2; - } - return 0; - } - - private static byte[] compress(byte[] input, CompressionType compressionType) { - switch (compressionType) { - case NONE: - return input; - case GZIP: - return GzipUtil.deflate(input); - case LZMA: - return CompressLzma.compress(input); - } - return null; - } - - // Note: if writableFontData is null, just return the size - private static int writeBase128(WritableFontData writableFontData, long value, int offset) { - int size = 1; - long tmpValue = value; - while (tmpValue >= 128) { - size += 1; - tmpValue = tmpValue >> 7; - } - for (int i = 0; i < size; i++) { - int b = (int)(value >> (7 * (size - i - 1))) & 0x7f; - if (i < size - 1) { - b |= 0x80; - } - if (writableFontData != null) { - writableFontData.writeByte(offset, (byte)b); - } - offset += 1; - } - return size; - } - - private class TableDirectoryEntry { - private final long tag; - private final long flags; - private final long origLength; - private final long transformLength; - private final byte[] bytes; - - // This is the constructor for tables that don't have transforms - public TableDirectoryEntry(long tag, byte[] uncompressedBytes, - CompressionType compressionType) { - this(tag, uncompressedBytes, uncompressedBytes, 0, compressionType); - } - - public TableDirectoryEntry(long tag, byte[] uncompressedBytes, byte[] transformedBytes, - long transformFlags, CompressionType compressionType) { - byte[] compressedBytes = compress(transformedBytes, compressionType); - if (compressedBytes.length >= transformedBytes.length) { - compressedBytes = transformedBytes; - compressionType = CompressionType.NONE; - } - this.tag = tag; - this.flags = transformFlags | flagsForCompression(compressionType); - this.origLength = uncompressedBytes.length; - this.transformLength = transformedBytes.length; - this.bytes = compressedBytes; - } - - public long getOrigLength() { - return origLength; - } - - public long getCompLength() { - return bytes.length; - } - - // Note: if writableFontData is null, just return the size - public int writeEntry(WritableFontData writableFontData, int offset) { - if (longForm) { - if (writableFontData != null) { - offset += writableFontData.writeULong(offset, tag); - offset += writableFontData.writeULong(offset, flags); - offset += writableFontData.writeULong(offset, getCompLength()); - offset += writableFontData.writeULong(offset, transformLength); - offset += writableFontData.writeULong(offset, getOrigLength()); - } - return TABLE_ENTRY_SIZE; - } else { - int start = offset; - int flag_byte = 0x1f; - if (KNOWN_TABLES.containsKey((int)tag)) { - flag_byte = KNOWN_TABLES.get((int)tag); - } - if ((flags & FLAG_APPLY_TRANSFORM) != 0) { - flag_byte |= 0x20; - } - if ((flags & FLAG_CONTINUE_STREAM) != 0) { - flag_byte |= 0xc0; - } else { - flag_byte |= (flags & 3) << 6; - } - if (writableFontData != null) { -// System.out.printf("%d: tag = %08x, flag = %02x\n", offset, tag, flag_byte); - writableFontData.writeByte(offset, (byte)flag_byte); - } - offset += 1; - if ((flag_byte & 0x1f) == 0x1f) { - if (writableFontData != null) { - writableFontData.writeULong(offset, tag); - } - offset += 4; - } - offset += writeBase128(writableFontData, getOrigLength(), offset); - if ((flag_byte & 0x20) != 0) { - offset += writeBase128(writableFontData, transformLength, offset); - } - if ((flag_byte & 0xc0) == 0x40 || (flag_byte & 0xc0) == 0x80) { - offset += writeBase128(writableFontData, getCompLength(), offset); - } - return offset - start; - } - } - - public int writeData(WritableFontData writableFontData, int offset) { - writableFontData.writeBytes(offset, bytes); - return bytes.length; - } - } -} |