mirror of https://github.com/freetype/freetype
[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).
This commit is contained in:
parent
8c1e3fc339
commit
e496cb2980
16
ChangeLog
16
ChangeLog
|
@ -1,3 +1,19 @@
|
|||
2019-08-27 Nikhil Ramakrishnan <ramakrishnan.nikhil@gmail.com>
|
||||
|
||||
[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 <ramakrishnan.nikhil@gmail.com>
|
||||
|
||||
[sfnt] Include `woff2tags.c' for building.
|
||||
|
|
|
@ -104,6 +104,42 @@ FT_BEGIN_HEADER
|
|||
} WOFF_TableRec, *WOFF_Table;
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
*
|
||||
* @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:
|
||||
|
@ -123,7 +159,7 @@ FT_BEGIN_HEADER
|
|||
* 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_BEGIN_HEADER
|
|||
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;
|
||||
|
||||
|
|
|
@ -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,6 +33,57 @@
|
|||
#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,12 +165,23 @@
|
|||
{
|
||||
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[] =
|
||||
{
|
||||
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue