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.
This commit is contained in:
Werner Lemberg 2013-08-29 17:53:40 +02:00
parent dc240524ff
commit d689d1cf78
6 changed files with 507 additions and 8 deletions

View File

@ -1,3 +1,36 @@
2013-08-28 Werner Lemberg <wl@gnu.org>
Behdad Esfahbod <behdad@google.com>
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 <wl@gnu.org>
[gzip] New function `FT_Gzip_Uncompress'.

View File

@ -137,6 +137,71 @@ FT_BEGIN_HEADER
} TT_TableRec, *TT_Table;
/*************************************************************************/
/* */
/* <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> */

View File

@ -100,6 +100,7 @@ FT_BEGIN_HEADER
#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

View File

@ -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,10 +1142,11 @@
{
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;

View File

@ -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 &&
@ -480,6 +872,9 @@
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 ));
if ( face_index < 0 )

View File

@ -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;