ref: e496cb2980978be5b4b7a3721135a9d6a5d68aa8
parent: 8c1e3fc339eeb66d2de526425ca21794161c0346
author: Nikhil Ramakrishnan <[email protected]>
date: Sun Jun 16 14:56:18 EDT 2019
[woff2] Read table and collection directory. * include/freetype/internal/wofftypes.h (WOFF2_TtcFontRec): New structure. (WOFF2_HeaderRec): Add more fields. * src/sfnt/sfwoff2.c (READ_255USHORT, READ_BASE128, ROUND4): New macros. (Read255UShort, CollectionHeaderSize, compute_first_table_offset): New functions. (ReadBase128): Use `FT_READ_BYTE'. (woff2_open_font): Add functionality to read table directory and collection directory (if present).
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,21 @@
2019-08-27 Nikhil Ramakrishnan <[email protected]>
+ [woff2] Read table and collection directory.
+
+ * include/freetype/internal/wofftypes.h (WOFF2_TtcFontRec): New
+ structure.
+ (WOFF2_HeaderRec): Add more fields.
+
+ * src/sfnt/sfwoff2.c (READ_255USHORT, READ_BASE128, ROUND4): New
+ macros.
+ (Read255UShort, CollectionHeaderSize, compute_first_table_offset):
+ New functions.
+ (ReadBase128): Use `FT_READ_BYTE'.
+ (woff2_open_font): Add functionality to read table directory and
+ collection directory (if present).
+
+2019-08-27 Nikhil Ramakrishnan <[email protected]>
+
[sfnt] Include `woff2tags.c' for building.
* src/sfnt/rules.mk (SFNT_DRV_SRC): Add `woff2tags.c'.
--- a/include/freetype/internal/wofftypes.h
+++ b/include/freetype/internal/wofftypes.h
@@ -107,6 +107,42 @@
/**************************************************************************
*
* @struct:
+ * WOFF2_TtcFontRec
+ *
+ * @description:
+ * Metadata for a TTC font entry in WOFF2.
+ *
+ * @fields:
+ * flavor ::
+ * TTC font flavor.
+ *
+ * num_tables ::
+ * Number of tables in TTC, indicating number of elements in
+ * `table_indices`.
+ *
+ * dst_offset ::
+ * Uncompressed table offset.
+ *
+ * header_checksum ::
+ * Checksum for font header.
+ *
+ * table_indices ::
+ * Array of table indices for each TTC font.
+ */
+ typedef struct WOFF2_TtcFontRec_
+ {
+ FT_ULong flavor;
+ FT_UShort num_tables;
+ FT_ULong dst_offset;
+ FT_ULong header_checksum;
+ FT_UShort* table_indices;
+
+ } WOFF2_TtcFontRec, *WOFF2_TtcFont;
+
+
+ /**************************************************************************
+ *
+ * @struct:
* WOFF2_HeaderRec
*
* @description:
@@ -123,7 +159,7 @@
* does not necessarily represent the actual size of the uncompressed
* SFNT font stream, so that is not included either.
*/
- typedef struct WOFF2_HeaderRec
+ typedef struct WOFF2_HeaderRec_
{
FT_ULong signature;
FT_ULong flavor;
@@ -137,7 +173,11 @@
FT_ULong privLength;
FT_ULong uncompressed_size;
+ FT_UInt64 compressed_offset;
FT_ULong header_version;
+ FT_UShort num_fonts;
+
+ WOFF2_TtcFont ttc_fonts;
} WOFF2_HeaderRec, *WOFF2_Header;
--- a/src/sfnt/sfwoff2.c
+++ b/src/sfnt/sfwoff2.c
@@ -17,6 +17,7 @@
#include <ft2build.h>
#include "sfwoff2.h"
+#include "woff2tags.h"
#include FT_TRUETYPE_TAGS_H
#include FT_INTERNAL_DEBUG_H
#include FT_INTERNAL_STREAM_H
@@ -32,7 +33,58 @@
#define FT_COMPONENT sfwoff2
+#define READ_255USHORT( var ) Read255UShort( stream, &var )
+#define READ_BASE128( var ) ReadBase128( stream, &var )
+#define ROUND4( var ) ( var + 3 ) & ~3
+
+
static FT_Error
+ Read255UShort( FT_Stream stream,
+ FT_UShort* value )
+ {
+ static const FT_Int oneMoreByteCode1 = 255;
+ static const FT_Int oneMoreByteCode2 = 254;
+ static const FT_Int wordCode = 253;
+ static const FT_Int lowestUCode = 253;
+
+ FT_Error error;
+ FT_Byte code;
+ FT_Byte result_byte = 0;
+ FT_UShort result_short = 0;
+
+ if( FT_READ_BYTE( code ) )
+ return FT_THROW( Invalid_Table );
+ if( code == wordCode )
+ {
+ /* Read next two bytes and store UInt16 value */
+ if( FT_READ_USHORT( result_short ) )
+ return FT_THROW( Invalid_Table );
+ *value = result_short;
+ return FT_Err_Ok;
+ }
+ else if( code == oneMoreByteCode1 )
+ {
+ if( FT_READ_BYTE( result_byte ) )
+ return FT_THROW( Invalid_Table );
+ *value = result_byte + lowestUCode;
+ return FT_Err_Ok;
+ }
+ else if( code == oneMoreByteCode2 )
+ {
+ if( FT_READ_BYTE( result_byte ) )
+ return FT_THROW( Invalid_Table );
+ *value = result_byte + lowestUCode * 2;
+ return FT_Err_Ok;
+ }
+ else
+ {
+ *value = code;
+ return FT_Err_Ok;
+ }
+ }
+
+
+ static FT_Error
ReadBase128( FT_Stream stream,
FT_ULong* value )
{
@@ -39,11 +91,12 @@
FT_ULong result = 0;
FT_Int i;
FT_Byte code;
- FT_Byte* p = stream->cursor;
+ FT_Error error;
for ( i = 0; i < 5; ++i ) {
code = 0;
- code = FT_NEXT_BYTE( p );
+ if( FT_READ_BYTE( code ) )
+ return FT_THROW( Invalid_Table );
/* Leading zeros are invalid. */
if ( i == 0 && code == 0x80 ) {
@@ -68,6 +121,41 @@
}
+ static FT_Offset
+ CollectionHeaderSize( FT_ULong header_version,
+ FT_UShort num_fonts )
+ {
+ FT_Offset size = 0;
+ if (header_version == 0x00020000)
+ size += 12; /* ulDsig{Tag,Length,Offset} */
+ if (header_version == 0x00010000 || header_version == 0x00020000) {
+ size += 12 /* TTCTag, Version, numFonts */
+ + ( 4 * num_fonts ); /* OffsetTable[numFonts] */
+ }
+ return size;
+ }
+
+
+ static FT_Error
+ compute_first_table_offset( const WOFF2_Header woff2 )
+ {
+ FT_Int nn;
+ FT_Offset offset = WOFF2_SFNT_HEADER_SIZE +
+ ( WOFF2_SFNT_ENTRY_SIZE *
+ (FT_Offset)( woff2->num_tables ) );
+ if(woff2->header_version)
+ {
+ offset = CollectionHeaderSize( woff2->header_version,
+ woff2->num_fonts )
+ + WOFF2_SFNT_HEADER_SIZE * woff2->num_fonts;
+ for ( nn = 0; nn< woff2->num_fonts; nn++ ) {
+ offset += WOFF2_SFNT_ENTRY_SIZE * woff2->ttc_fonts[nn].num_tables;
+ }
+ }
+ return offset;
+ }
+
+
/* Replace `face->root.stream' with a stream containing the extracted */
/* SFNT of a WOFF2 font. */
@@ -77,13 +165,24 @@
{
FT_Memory memory = stream->memory;
FT_Error error = FT_Err_Ok;
- FT_Byte* p = stream->cursor;
- FT_Byte* limit = stream->limit;
WOFF2_HeaderRec woff2;
WOFF2_Table tables = NULL;
WOFF2_Table* indices = NULL;
+ WOFF2_Table last_table;
+ FT_Int nn;
+ FT_ULong j;
+ FT_ULong flags;
+ FT_UShort xform_version;
+ FT_ULong src_offset = 0;
+
+ FT_UShort ttc_num_tables;
+ FT_UInt glyf_index;
+ FT_UInt loca_index;
+ FT_UInt64 first_table_offset;
+ FT_UInt64 file_offset;
+
static const FT_Frame_Field woff2_header_fields[] =
{
#undef FT_STRUCTURE
@@ -105,14 +204,9 @@
FT_FRAME_END
};
- FT_UNUSED( p );
- FT_UNUSED( limit );
- FT_UNUSED( tables );
- FT_UNUSED( indices );
- FT_UNUSED( memory );
/* DEBUG - Remove later */
- FT_TRACE2(("woff2_open_font: Received Data.\n"));
+ FT_TRACE2(( "woff2_open_font: Received Data.\n" ));
FT_ASSERT( stream == face->root.stream );
FT_ASSERT( FT_STREAM_POS() == 0 );
@@ -122,14 +216,14 @@
return error;
/* DEBUG - Remove later. */
- FT_TRACE2(("signature -> 0x%X\n", woff2.signature));
- FT_TRACE2(("flavor -> 0x%X\n", woff2.flavor));
- FT_TRACE2(("length -> %lu\n", woff2.length));
- FT_TRACE2(("num_tables -> %hu\n", woff2.num_tables));
- FT_TRACE2(("metaOffset -> %hu\n", woff2.metaOffset));
- FT_TRACE2(("metaLength -> %hu\n", woff2.metaLength));
- FT_TRACE2(("privOffset -> %hu\n", woff2.privOffset));
- FT_TRACE2(("privLength -> %hu\n", woff2.privLength));
+ FT_TRACE2(( "signature -> 0x%X\n", woff2.signature ));
+ FT_TRACE2(( "flavor -> 0x%X\n", woff2.flavor ));
+ FT_TRACE2(( "length -> %lu\n", woff2.length ));
+ FT_TRACE2(( "num_tables -> %hu\n", woff2.num_tables ));
+ FT_TRACE2(( "metaOffset -> %hu\n", woff2.metaOffset ));
+ FT_TRACE2(( "metaLength -> %hu\n", woff2.metaLength ));
+ FT_TRACE2(( "privOffset -> %hu\n", woff2.privOffset ));
+ FT_TRACE2(( "privLength -> %hu\n", woff2.privLength ));
/* Make sure we don't recurse back here. */
if ( woff2.flavor == TTAG_wOF2 )
@@ -153,12 +247,205 @@
}
/* DEBUG - Remove later. */
else{
- FT_TRACE2(("WOFF2 Header is valid.\n"));
+ FT_TRACE2(( "WOFF2 Header is valid.\n" ));
}
- /* TODO Read table directory. */
+ /* Read table directory. */
+ if ( FT_NEW_ARRAY( tables, woff2.num_tables ) ||
+ FT_NEW_ARRAY( indices, woff2.num_tables ) )
+ goto Exit;
+ FT_TRACE2(( "\n"
+ " tag flags transform origLen transformLen\n"
+ " --------------------------------------------------\n" ));
+
+ /* TODO check whether there is sufficient input before FT_READ_*. */
+ for ( nn = 0; nn < woff2.num_tables; nn++ )
+ {
+ WOFF2_Table table = tables + nn;
+ if( FT_READ_BYTE( table->FlagByte ) )
+ goto Exit;
+
+ if ( ( table->FlagByte & 0x3f ) == 0x3f )
+ {
+ if( FT_READ_ULONG( table->Tag ) )
+ goto Exit;
+ }
+ else
+ table->Tag = KnownTags[table->FlagByte & 0x3f];
+
+ flags = 0;
+ xform_version = ( table->FlagByte >> 6 ) & 0x03;
+
+ /* 0 means xform for glyph/loca, non-0 for others. */
+ if ( table->Tag == TTAG_glyf || table->Tag == TTAG_loca )
+ {
+ if ( xform_version == 0 )
+ flags |= WOFF2_FLAGS_TRANSFORM;
+ }
+ else if ( xform_version != 0 )
+ flags |= WOFF2_FLAGS_TRANSFORM;
+
+ flags |= xform_version;
+
+ if( READ_BASE128( table->OrigLength ) )
+ goto Exit;
+
+ table->TransformLength = table->OrigLength;
+
+ if ( ( flags & WOFF2_FLAGS_TRANSFORM ) != 0 )
+ {
+ if( READ_BASE128( table->TransformLength ) )
+ goto Exit;
+ if( table->Tag == TTAG_loca && table->TransformLength )
+ {
+ FT_ERROR(( "woff_font_open: Invalid loca `transformLength'.\n" ));
+ return FT_THROW( Invalid_Table );
+ }
+ }
+
+ if ( src_offset + table->TransformLength < src_offset )
+ {
+ FT_ERROR(( "woff_font_open: invalid WOFF2 table directory.\n" ));
+ return FT_THROW( Invalid_Table );
+ }
+
+ table->src_offset = src_offset;
+ table->src_length = table->TransformLength;
+ src_offset += table->TransformLength;
+ table->flags = flags;
+
+ FT_TRACE2(( " %c%c%c%c %08d %08d %08ld %08ld\n",
+ (FT_Char)( table->Tag >> 24 ),
+ (FT_Char)( table->Tag >> 16 ),
+ (FT_Char)( table->Tag >> 8 ),
+ (FT_Char)( table->Tag ),
+ table->FlagByte & 0x3f,
+ ( table->FlagByte >> 6 ) & 0x03,
+ table->OrigLength,
+ table->TransformLength,
+ table->src_length,
+ table->src_offset ));
+
+ indices[nn] = table;
+ }
+
+ /* End of last table is uncompressed size. */
+ last_table = indices[woff2.num_tables - 1];
+ woff2.uncompressed_size = last_table->src_offset
+ + last_table->src_length;
+ if( woff2.uncompressed_size < last_table->src_offset )
+ return FT_THROW( Invalid_Table );
+ /* DEBUG - Remove later. */
+ FT_TRACE2(( "Uncompressed size: %ld\n", woff2.uncompressed_size ));
+
+ FT_TRACE2(( "Table directory successfully parsed.\n" ));
+
+ /* Check for and read collection directory. */
+ woff2.header_version = 0;
+ if( woff2.flavor == TTAG_ttcf ){
+ FT_TRACE2(( "Font is a TTC, reading collection directory.\n" ));
+ if( FT_READ_ULONG( woff2.header_version ) )
+ goto Exit;
+ /* DEBUG - Remove later */
+ FT_TRACE2(( "Header version: %lx\n", woff2.header_version ));
+ if( woff2.header_version != 0x00010000 &&
+ woff2.header_version != 0x00020000 )
+ return FT_THROW( Invalid_Table );
+
+ if( READ_255USHORT( woff2.num_fonts ) )
+ goto Exit;
+ /* DEBUG - Remove later */
+ FT_TRACE2(( "Number of fonts in TTC: %ld\n", woff2.num_fonts ));
+
+ if( FT_NEW_ARRAY( woff2.ttc_fonts, woff2.num_fonts ) )
+ goto Exit;
+
+ for ( nn = 0; nn < woff2.num_fonts; nn++ )
+ {
+ WOFF2_TtcFont ttc_font = woff2.ttc_fonts + nn;
+
+ if( READ_255USHORT( ttc_num_tables ) )
+ goto Exit;
+ if( FT_READ_ULONG( ttc_font->flavor ) )
+ goto Exit;
+
+ if( FT_NEW_ARRAY( ttc_font->table_indices, ttc_num_tables ) )
+ goto Exit;
+ /* DEBUG - Change to TRACE4 */
+ FT_TRACE2(( "Number of tables in font %d: %ld\n",
+ nn, ttc_num_tables ));
+ /* DEBUG - Change to TRACE5 */
+ FT_TRACE2(( " Indices: " ));
+
+ glyf_index = 0;
+ loca_index = 0;
+
+ for ( j = 0; j < ttc_num_tables; j++ )
+ {
+ FT_UShort table_index;
+ WOFF2_Table table;
+
+ if( READ_255USHORT( table_index ) )
+ goto Exit;
+ /* DEBUG - Change to TRACE5 */
+ FT_TRACE2(("%hu ", table_index));
+ ttc_font->table_indices[j] = table_index;
+
+ table = indices[table_index];
+ if( table->Tag == TTAG_loca )
+ loca_index = table_index;
+ if( table->Tag == TTAG_glyf )
+ glyf_index = table_index;
+ }
+ /* DEBUG - Change to TRACE5 */
+ FT_TRACE2(( "\n" ));
+
+ /* glyf and loca must be consecutive */
+ if( glyf_index > 0 || loca_index > 0 )
+ {
+ if(glyf_index > loca_index || loca_index - glyf_index != 1)
+ return FT_THROW( Invalid_Table );
+ /* DEBUG - Remove later */
+ else
+ FT_TRACE2(( "Glyf and loca are valid.\n" ));
+ }
+ }
+ /* Collection directory reading complete. */
+ FT_TRACE2(( "WOFF2 collection dirtectory is valid.\n" ));
+
+ }
+
+ first_table_offset = compute_first_table_offset( &woff2 );
+ FT_TRACE2(( "Offset to first table: %ld\n", first_table_offset ));
+
+ woff2.compressed_offset = FT_STREAM_POS();
+ file_offset = ROUND4( woff2.compressed_offset +
+ woff2.totalCompressedSize );
+
+ /* Few more checks before we start reading the tables */
+ if( file_offset > woff2.length )
+ return FT_THROW( Invalid_Table );
+
+ if ( woff2.metaOffset )
+ {
+ if ( file_offset != woff2.metaOffset )
+ return FT_THROW( Invalid_Table );
+ file_offset = ROUND4(woff2.metaOffset + woff2.metaLength);
+ }
+
+ if( woff2.privOffset )
+ {
+ if( file_offset != woff2.privOffset )
+ return FT_THROW( Invalid_Table );
+ file_offset = ROUND4(woff2.privOffset + woff2.privLength);
+ }
+
+ if( file_offset != ( ROUND4( woff2.length ) ) )
+ return FT_THROW( Invalid_Table );
+
error = FT_THROW( Unimplemented_Feature );
+ FT_TRACE2(( "Reached end without errors.\n" ));
goto Exit;
Exit: