ref: d689d1cf782fe52f4d8def8c790825f01dd39059
parent: dc240524ff31891a442225430b28e9620c1fa89f
author: Werner Lemberg <[email protected]>
date: Thu Aug 29 13:53:40 EDT 2013
Implement support for WOFF containers. We simply synthesize a SFNT from the WOFF, create a memory stream for the new data, and load the SFNT as usual. Does NOT add any API to access WOFF metadata or private blocks. * include/freetype/internal/tttypes.h (WOFF_HeaderRec, WOFF_TableRec): New structures. * include/freetype/tttags.h (TTAG_wOFF): New macro. * src/base/ftobjs.c (FT_Open_Face): Set `stream' after calling `open_face'. * src/sfnt/sfobjs.c [FT_CONFIG_OPTION_SYSTEM_ZLIB]: Include `FT_GZIP_H'. (WRITE_BYTE, WRITE_USHORT, WRITE_ULONG): New temporary macros for writing to a stream. (sfnt_stream_close, compare_offsets, woff_open_font): New functions. (sfnt_open_font): Handle `TTAG_wOFF'. (sfnt_init_face): Set `stream' after calling `sfnt_open_font'. * src/truetype/ttobjs.c (tt_face_init): Set `stream' after calling `sfnt->init_face'. * src/base/ftobjs.c (open_face): Use a pointer to FT_Stream as an argument so that a changed stream survives. Update callers.
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,37 @@
2013-08-28 Werner Lemberg <[email protected]>
+ Behdad Esfahbod <[email protected]>
+
+ Implement support for WOFF containers.
+
+ We simply synthesize a SFNT from the WOFF, create a memory stream
+ for the new data, and load the SFNT as usual.
+
+ Does NOT add any API to access WOFF metadata or private blocks.
+
+ * include/freetype/internal/tttypes.h (WOFF_HeaderRec,
+ WOFF_TableRec): New structures.
+
+ * include/freetype/tttags.h (TTAG_wOFF): New macro.
+
+ * src/base/ftobjs.c (FT_Open_Face): Set `stream' after calling
+ `open_face'.
+
+ * src/sfnt/sfobjs.c [FT_CONFIG_OPTION_SYSTEM_ZLIB]: Include
+ `FT_GZIP_H'.
+ (WRITE_BYTE, WRITE_USHORT, WRITE_ULONG): New temporary macros for
+ writing to a stream.
+ (sfnt_stream_close, compare_offsets, woff_open_font): New functions.
+ (sfnt_open_font): Handle `TTAG_wOFF'.
+ (sfnt_init_face): Set `stream' after calling `sfnt_open_font'.
+
+ * src/truetype/ttobjs.c (tt_face_init): Set `stream' after calling
+ `sfnt->init_face'.
+
+ * src/base/ftobjs.c (open_face): Use a pointer to FT_Stream as an
+ argument so that a changed stream survives.
+ Update callers.
+
+2013-08-28 Werner Lemberg <[email protected]>
[gzip] New function `FT_Gzip_Uncompress'.
--- a/include/freetype/internal/tttypes.h
+++ b/include/freetype/internal/tttypes.h
@@ -140,6 +140,71 @@
/*************************************************************************/
/* */
/* <Struct> */
+ /* WOFF_HeaderRec */
+ /* */
+ /* <Description> */
+ /* WOFF file format header. */
+ /* */
+ /* <Fields> */
+ /* See */
+ /* */
+ /* http://www.w3.org/TR/WOFF/#WOFFHeader */
+ /* */
+ typedef struct WOFF_HeaderRec_
+ {
+ FT_ULong signature;
+ FT_ULong flavor;
+ FT_ULong length;
+ FT_UShort num_tables;
+ FT_UShort reserved;
+ FT_ULong totalSfntSize;
+ FT_UShort majorVersion;
+ FT_UShort minorVersion;
+ FT_ULong metaOffset;
+ FT_ULong metaLength;
+ FT_ULong metaOrigLength;
+ FT_ULong privOffset;
+ FT_ULong privLength;
+
+ } WOFF_HeaderRec, *WOFF_Header;
+
+
+ /*************************************************************************/
+ /* */
+ /* <Struct> */
+ /* WOFF_TableRec */
+ /* */
+ /* <Description> */
+ /* This structure describes a given table of a WOFF font. */
+ /* */
+ /* <Fields> */
+ /* Tag :: A four-bytes tag describing the table. */
+ /* */
+ /* Offset :: The offset of the table from the start of the WOFF */
+ /* font in its resource. */
+ /* */
+ /* CompLength :: Compressed table length (in bytes). */
+ /* */
+ /* OrigLength :: Unompressed table length (in bytes). */
+ /* */
+ /* CheckSum :: The table checksum. This value can be ignored. */
+ /* */
+ typedef struct WOFF_TableRec_
+ {
+ FT_ULong Tag; /* table ID */
+ FT_ULong Offset; /* table file offset */
+ FT_ULong CompLength; /* compressed table length */
+ FT_ULong OrigLength; /* uncompressed table length */
+ FT_ULong CheckSum; /* uncompressed checksum */
+
+ FT_ULong OrigOffset; /* uncompressed table file offset */
+ /* (not in the WOFF file) */
+ } WOFF_TableRec, *WOFF_Table;
+
+
+ /*************************************************************************/
+ /* */
+ /* <Struct> */
/* TT_LongMetricsRec */
/* */
/* <Description> */
--- a/include/freetype/tttags.h
+++ b/include/freetype/tttags.h
@@ -100,6 +100,7 @@
#define TTAG_VDMX FT_MAKE_TAG( 'V', 'D', 'M', 'X' )
#define TTAG_vhea FT_MAKE_TAG( 'v', 'h', 'e', 'a' )
#define TTAG_vmtx FT_MAKE_TAG( 'v', 'm', 't', 'x' )
+#define TTAG_wOFF FT_MAKE_TAG( 'w', 'O', 'F', 'F' )
FT_END_HEADER
--- a/src/base/ftobjs.c
+++ b/src/base/ftobjs.c
@@ -1133,7 +1133,7 @@
/* */
static FT_Error
open_face( FT_Driver driver,
- FT_Stream stream,
+ FT_Stream *astream,
FT_Bool external_stream,
FT_Long face_index,
FT_Int num_params,
@@ -1142,11 +1142,12 @@
{
FT_Memory memory;
FT_Driver_Class clazz;
- FT_Face face = 0;
- FT_Error error, error2;
+ FT_Face face = NULL;
FT_Face_Internal internal = NULL;
+ FT_Error error, error2;
+
clazz = driver->clazz;
memory = driver->root.memory;
@@ -1156,13 +1157,12 @@
face->driver = driver;
face->memory = memory;
- face->stream = stream;
+ face->stream = *astream;
/* set the FT_FACE_FLAG_EXTERNAL_STREAM bit for FT_Done_Face */
if ( external_stream )
face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM;
-
if ( FT_NEW( internal ) )
goto Fail;
@@ -1183,11 +1183,12 @@
#endif
if ( clazz->init_face )
- error = clazz->init_face( stream,
+ error = clazz->init_face( *astream,
face,
(FT_Int)face_index,
num_params,
params );
+ *astream = face->stream; /* Stream may have been changed. */
if ( error )
goto Fail;
@@ -2075,7 +2076,7 @@
params = args->params;
}
- error = open_face( driver, stream, external_stream, face_index,
+ error = open_face( driver, &stream, external_stream, face_index,
num_params, params, &face );
if ( !error )
goto Success;
@@ -2111,7 +2112,7 @@
params = args->params;
}
- error = open_face( driver, stream, external_stream, face_index,
+ error = open_face( driver, &stream, external_stream, face_index,
num_params, params, &face );
if ( !error )
goto Success;
--- a/src/sfnt/sfobjs.c
+++ b/src/sfnt/sfobjs.c
@@ -27,6 +27,7 @@
#include FT_TRUETYPE_TAGS_H
#include FT_SERVICE_POSTSCRIPT_CMAPS_H
#include FT_SFNT_NAMES_H
+#include FT_GZIP_H
#include "sferrors.h"
#ifdef TT_CONFIG_OPTION_BDF
@@ -347,6 +348,380 @@
}
+#define WRITE_BYTE( p, v ) \
+ do \
+ { \
+ *(p)++ = (v) >> 0; \
+ \
+ } while ( 0 )
+
+#define WRITE_USHORT( p, v ) \
+ do \
+ { \
+ *(p)++ = (v) >> 8; \
+ *(p)++ = (v) >> 0; \
+ \
+ } while ( 0 )
+
+#define WRITE_ULONG( p, v ) \
+ do \
+ { \
+ *(p)++ = (v) >> 24; \
+ *(p)++ = (v) >> 16; \
+ *(p)++ = (v) >> 8; \
+ *(p)++ = (v) >> 0; \
+ \
+ } while (0)
+
+
+ static void
+ sfnt_stream_close( FT_Stream stream )
+ {
+ FT_Memory memory = stream->memory;
+
+
+ FT_FREE( stream->base );
+
+ stream->size = 0;
+ stream->base = 0;
+ stream->close = 0;
+ }
+
+
+ FT_CALLBACK_DEF( int )
+ compare_offsets( const void* a,
+ const void* b )
+ {
+ WOFF_Table table1 = *(WOFF_Table*)a;
+ WOFF_Table table2 = *(WOFF_Table*)b;
+
+ FT_ULong offset1 = table1->Offset;
+ FT_ULong offset2 = table2->Offset;
+
+
+ if ( offset1 > offset2 )
+ return 1;
+ else if ( offset1 < offset2 )
+ return -1;
+ else
+ return 0;
+ }
+
+
+ /* Replace `face->root.stream' with a stream containing the extracted */
+ /* sfnt of a woff font. */
+
+ static FT_Error
+ woff_open_font( FT_Stream stream,
+ TT_Face face )
+ {
+ FT_Memory memory = stream->memory;
+ FT_Error error = FT_Err_Ok;
+
+ WOFF_HeaderRec woff;
+ WOFF_Table tables = NULL;
+ WOFF_Table* indices = NULL;
+
+ FT_ULong woff_offset;
+
+ FT_Byte* sfnt = NULL;
+ FT_Stream sfnt_stream = NULL;
+
+ FT_Byte* sfnt_header;
+ FT_ULong sfnt_offset;
+
+ FT_Int nn;
+ FT_ULong old_tag = 0;
+
+ static const FT_Frame_Field woff_header_fields[] =
+ {
+#undef FT_STRUCTURE
+#define FT_STRUCTURE WOFF_HeaderRec
+
+ FT_FRAME_START( 44 ),
+ FT_FRAME_ULONG ( signature ),
+ FT_FRAME_ULONG ( flavor ),
+ FT_FRAME_ULONG ( length ),
+ FT_FRAME_USHORT( num_tables ),
+ FT_FRAME_USHORT( reserved ),
+ FT_FRAME_ULONG ( totalSfntSize ),
+ FT_FRAME_USHORT( majorVersion ),
+ FT_FRAME_USHORT( minorVersion ),
+ FT_FRAME_ULONG ( metaOffset ),
+ FT_FRAME_ULONG ( metaLength ),
+ FT_FRAME_ULONG ( metaOrigLength ),
+ FT_FRAME_ULONG ( privOffset ),
+ FT_FRAME_ULONG ( privLength ),
+ FT_FRAME_END
+ };
+
+
+ FT_ASSERT( stream == face->root.stream );
+ FT_ASSERT( FT_STREAM_POS() == 0 );
+
+ if ( FT_STREAM_READ_FIELDS( woff_header_fields, &woff ) )
+ return error;
+
+ /* Make sure we don't recurse back here or hit ttc code. */
+ if ( woff.flavor == TTAG_wOFF || woff.flavor == TTAG_ttcf )
+ return FT_THROW( Invalid_Table );
+
+ /* Miscellaneous checks. */
+ if ( woff.length != stream->size ||
+ woff.num_tables == 0 ||
+ 44 + woff.num_tables * 20UL >= woff.length ||
+ 12 + woff.num_tables * 16UL >= woff.totalSfntSize ||
+ ( woff.totalSfntSize & 3 ) != 0 ||
+ ( woff.metaOffset == 0 && ( woff.metaLength != 0 ||
+ woff.metaOrigLength != 0 ) ) ||
+ ( woff.metaLength != 0 && woff.metaOrigLength == 0 ) ||
+ ( woff.privOffset == 0 && woff.privLength != 0 ) )
+ return FT_THROW( Invalid_Table );
+
+ if ( FT_ALLOC( sfnt, woff.totalSfntSize ) ||
+ FT_NEW( sfnt_stream ) )
+ goto Exit;
+
+ sfnt_header = sfnt;
+
+ /* Write sfnt header. */
+ {
+ FT_UInt searchRange, entrySelector, rangeShift, x;
+
+
+ x = woff.num_tables;
+ entrySelector = 0;
+ while ( x )
+ {
+ x >>= 1;
+ entrySelector += 1;
+ }
+ entrySelector--;
+
+ searchRange = ( 1 << entrySelector ) * 16;
+ rangeShift = woff.num_tables * 16 - searchRange;
+
+ WRITE_ULONG ( sfnt_header, woff.flavor );
+ WRITE_USHORT( sfnt_header, woff.num_tables );
+ WRITE_USHORT( sfnt_header, searchRange );
+ WRITE_USHORT( sfnt_header, entrySelector );
+ WRITE_USHORT( sfnt_header, rangeShift );
+ }
+
+ /* While the entries in the sfnt header must be sorted by the */
+ /* tag value, the tables themselves are not. We thus have to */
+ /* sort them by offset and check that they don't overlap. */
+
+ if ( FT_NEW_ARRAY( tables, woff.num_tables ) ||
+ FT_NEW_ARRAY( indices, woff.num_tables ) )
+ goto Exit;
+
+ FT_TRACE2(( "\n"
+ " tag offset compLen origLen checksum\n"
+ " -------------------------------------------\n" ));
+
+ for ( nn = 0; nn < woff.num_tables; nn++ )
+ {
+ WOFF_Table table = tables + nn;
+
+
+ if ( FT_STREAM_SEEK( 44 + nn * 20 ) ||
+ FT_FRAME_ENTER( 20L ) )
+ goto Exit;
+
+ table->Tag = FT_GET_TAG4();
+ table->Offset = FT_GET_ULONG();
+ table->CompLength = FT_GET_ULONG();
+ table->OrigLength = FT_GET_ULONG();
+ table->CheckSum = FT_GET_ULONG();
+
+ FT_FRAME_EXIT();
+
+ FT_TRACE2(( " %c%c%c%c %08lx %08lx %08lx %08lx\n",
+ (FT_Char)( table->Tag >> 24 ),
+ (FT_Char)( table->Tag >> 16 ),
+ (FT_Char)( table->Tag >> 8 ),
+ (FT_Char)( table->Tag ),
+ table->Offset,
+ table->CompLength,
+ table->OrigLength,
+ table->CheckSum ));
+
+ if ( table->Tag <= old_tag )
+ {
+ error = FT_THROW( Invalid_Table );
+ goto Exit;
+ }
+
+ old_tag = table->Tag;
+ indices[nn] = table;
+ }
+
+ /* Sort by offset. */
+
+ ft_qsort( indices,
+ woff.num_tables,
+ sizeof ( WOFF_Table ),
+ compare_offsets );
+
+ /* Check offsets and lengths. */
+
+ woff_offset = 44 + woff.num_tables * 20L;
+ sfnt_offset = 12 + woff.num_tables * 16L;
+
+ for ( nn = 0; nn < woff.num_tables; nn++ )
+ {
+ WOFF_Table table = indices[nn];
+
+
+ if ( table->Offset != woff_offset ||
+ table->Offset + table->CompLength > woff.length ||
+ sfnt_offset + table->OrigLength > woff.totalSfntSize ||
+ table->CompLength > table->OrigLength )
+ {
+ error = FT_THROW( Invalid_Table );
+ goto Exit;
+ }
+
+ table->OrigOffset = sfnt_offset;
+
+ /* The offsets must be multiples of 4. */
+ woff_offset += ( table->CompLength + 3 ) & ~3;
+ sfnt_offset += ( table->OrigLength + 3 ) & ~3;
+ }
+
+ /*
+ * Final checks!
+ *
+ * We don't decode and check the metadata block.
+ * We don't check table checksums either.
+ * But other than those, I think we implement all
+ * `MUST' checks from the spec.
+ */
+
+ if ( woff.metaOffset )
+ {
+ if ( woff.metaOffset != woff_offset ||
+ woff.metaOffset + woff.metaLength > woff.length )
+ {
+ error = FT_THROW( Invalid_Table );
+ goto Exit;
+ }
+
+ /* We have padding only ... */
+ woff_offset += woff.metaLength;
+ }
+
+ if ( woff.privOffset )
+ {
+ /* ... if it isn't the last block. */
+ woff_offset = ( woff_offset + 3 ) & ~3;
+
+ if ( woff.privOffset != woff_offset ||
+ woff.privOffset + woff.privLength > woff.length )
+ {
+ error = FT_THROW( Invalid_Table );
+ goto Exit;
+ }
+
+ /* No padding for the last block. */
+ woff_offset += woff.privLength;
+ }
+
+ if ( sfnt_offset != woff.totalSfntSize ||
+ woff_offset != woff.length )
+ {
+ error = FT_THROW( Invalid_Table );
+ goto Exit;
+ }
+
+ /* Write the tables. */
+
+ for ( nn = 0; nn < woff.num_tables; nn++ )
+ {
+ WOFF_Table table = tables + nn;
+
+
+ /* Write SFNT table entry. */
+ WRITE_ULONG( sfnt_header, table->Tag );
+ WRITE_ULONG( sfnt_header, table->CheckSum );
+ WRITE_ULONG( sfnt_header, table->OrigOffset );
+ WRITE_ULONG( sfnt_header, table->OrigLength );
+
+ /* Write table data. */
+ if ( FT_STREAM_SEEK( table->Offset ) ||
+ FT_FRAME_ENTER( table->CompLength ) )
+ goto Exit;
+
+ if ( table->CompLength == table->OrigLength )
+ {
+ /* Uncompressed data; just copy. */
+ ft_memcpy( sfnt + table->OrigOffset,
+ stream->cursor,
+ table->OrigLength );
+ }
+ else
+ {
+ /* Uncompress with zlib. */
+ FT_ULong output_len = table->OrigLength;
+
+
+ error = FT_Gzip_Uncompress( memory,
+ sfnt + table->OrigOffset, &output_len,
+ stream->cursor, table->CompLength );
+ if ( error )
+ goto Exit;
+ if ( output_len != table->OrigLength )
+ {
+ error = FT_THROW( Invalid_Table );
+ goto Exit;
+ }
+ }
+
+ FT_FRAME_EXIT();
+
+ /* We don't check whether the padding bytes in the WOFF file are */
+ /* actually '\0'. For the output, however, we do set them properly. */
+ sfnt_offset = table->OrigOffset + table->OrigLength;
+ while ( sfnt_offset & 3 )
+ {
+ sfnt[sfnt_offset] = '\0';
+ sfnt_offset++;
+ }
+ }
+
+ /* Ok! Finally ready. Swap out stream and return. */
+ FT_Stream_OpenMemory( sfnt_stream, sfnt, woff.totalSfntSize );
+ sfnt_stream->memory = stream->memory;
+ sfnt_stream->close = sfnt_stream_close;
+
+ FT_Stream_Free(
+ face->root.stream,
+ ( face->root.face_flags & FT_FACE_FLAG_EXTERNAL_STREAM ) != 0 );
+
+ stream = face->root.stream = sfnt_stream;
+
+ face->root.face_flags &= ~FT_FACE_FLAG_EXTERNAL_STREAM;
+
+ Exit:
+ FT_FREE( tables );
+ FT_FREE( indices );
+
+ if ( error )
+ {
+ FT_FREE( sfnt );
+ FT_Stream_Close( sfnt_stream );
+ FT_FREE( sfnt_stream );
+ }
+
+ return error;
+ }
+
+#undef WRITE_BYTE
+#undef WRITE_USHORT
+#undef WRITE_ULONG
+
+
/* Fill in face->ttc_header. If the font is not a TTC, it is */
/* synthesized into a TTC with one offset table. */
static FT_Error
@@ -373,11 +748,28 @@
face->ttc_header.version = 0;
face->ttc_header.count = 0;
+ retry:
offset = FT_STREAM_POS();
if ( FT_READ_ULONG( tag ) )
return error;
+ if ( tag == TTAG_wOFF )
+ {
+ FT_TRACE2(( "sfnt_open_font: file is a WOFF; synthesizing SFNT\n" ));
+
+ if ( FT_STREAM_SEEK( offset ) )
+ return error;
+
+ error = woff_open_font( stream, face );
+ if ( error )
+ return error;
+
+ /* Swap out stream and retry! */
+ stream = face->root.stream;
+ goto retry;
+ }
+
if ( tag != 0x00010000UL &&
tag != TTAG_ttcf &&
tag != TTAG_OTTO &&
@@ -479,6 +871,9 @@
error = sfnt_open_font( stream, face );
if ( error )
return error;
+
+ /* Stream may have changed in sfnt_open_font. */
+ stream = face->root.stream;
FT_TRACE2(( "sfnt_init_face: %08p, %ld\n", face, face_index ));
--- a/src/truetype/ttobjs.c
+++ b/src/truetype/ttobjs.c
@@ -532,6 +532,10 @@
/* check that we have a valid TrueType file */
error = sfnt->init_face( stream, face, face_index, num_params, params );
+
+ /* Stream may have changed. */
+ stream = face->root.stream;
+
if ( error )
goto Exit;