2019-06-11 20:51:20 +02:00
|
|
|
/****************************************************************************
|
|
|
|
*
|
|
|
|
* sfwoff2.c
|
|
|
|
*
|
|
|
|
* WOFF2 format management (base).
|
|
|
|
*
|
|
|
|
* Copyright (C) 2019 by
|
|
|
|
* Nikhil Ramakrishnan, David Turner, Robert Wilhelm, and Werner Lemberg.
|
|
|
|
*
|
|
|
|
* This file is part of the FreeType project, and may only be used,
|
|
|
|
* modified, and distributed under the terms of the FreeType project
|
|
|
|
* license, LICENSE.TXT. By continuing to use, modify, or distribute
|
|
|
|
* this file you indicate that you have read the license and
|
|
|
|
* understand and accept it fully.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <ft2build.h>
|
|
|
|
#include "sfwoff2.h"
|
2019-06-16 15:26:18 +02:00
|
|
|
#include "woff2tags.h"
|
2019-06-11 20:51:20 +02:00
|
|
|
#include FT_TRUETYPE_TAGS_H
|
|
|
|
#include FT_INTERNAL_DEBUG_H
|
|
|
|
#include FT_INTERNAL_STREAM_H
|
|
|
|
|
|
|
|
|
2019-06-30 01:01:04 +02:00
|
|
|
#ifdef FT_CONFIG_OPTION_USE_BROTLI
|
|
|
|
|
|
|
|
#include <brotli/decode.h>
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2019-06-11 20:51:20 +02:00
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* The macro FT_COMPONENT is used in trace mode. It is an implicit
|
|
|
|
* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
|
|
|
|
* messages during execution.
|
|
|
|
*/
|
|
|
|
#undef FT_COMPONENT
|
|
|
|
#define FT_COMPONENT sfwoff2
|
|
|
|
|
|
|
|
|
2019-06-16 15:26:18 +02:00
|
|
|
#define READ_255USHORT( var ) Read255UShort( stream, &var )
|
2019-06-23 21:37:18 +02:00
|
|
|
|
2019-06-16 15:26:18 +02:00
|
|
|
#define READ_BASE128( var ) ReadBase128( stream, &var )
|
2019-06-23 21:37:18 +02:00
|
|
|
|
2019-06-16 15:26:18 +02:00
|
|
|
#define ROUND4( var ) ( var + 3 ) & ~3
|
|
|
|
|
2019-06-23 21:37:18 +02:00
|
|
|
#define WRITE_USHORT( p, v ) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
*(p)++ = (FT_Byte)( (v) >> 8 ); \
|
|
|
|
*(p)++ = (FT_Byte)( (v) >> 0 ); \
|
|
|
|
\
|
|
|
|
} while ( 0 )
|
|
|
|
|
|
|
|
#define WRITE_ULONG( p, v ) \
|
|
|
|
do \
|
|
|
|
{ \
|
|
|
|
*(p)++ = (FT_Byte)( (v) >> 24 ); \
|
|
|
|
*(p)++ = (FT_Byte)( (v) >> 16 ); \
|
|
|
|
*(p)++ = (FT_Byte)( (v) >> 8 ); \
|
|
|
|
*(p)++ = (FT_Byte)( (v) >> 0 ); \
|
|
|
|
\
|
|
|
|
} while ( 0 )
|
|
|
|
|
|
|
|
|
|
|
|
FT_CALLBACK_DEF( int )
|
|
|
|
compare_tags( const void* a,
|
|
|
|
const void* b )
|
|
|
|
{
|
|
|
|
WOFF2_Table table1 = *(WOFF2_Table*)a;
|
|
|
|
WOFF2_Table table2 = *(WOFF2_Table*)b;
|
|
|
|
|
|
|
|
FT_ULong tag1 = table1->Tag;
|
|
|
|
FT_ULong tag2 = table2->Tag;
|
|
|
|
|
|
|
|
|
|
|
|
if ( tag1 > tag2 )
|
|
|
|
return 1;
|
|
|
|
else if ( tag1 < tag2 )
|
|
|
|
return -1;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-06-16 15:26:18 +02:00
|
|
|
|
|
|
|
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 )
|
|
|
|
{
|
2019-06-30 01:01:04 +02:00
|
|
|
/* Read next two bytes and store FT_UShort value */
|
2019-06-16 15:26:18 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-11 20:51:20 +02:00
|
|
|
static FT_Error
|
|
|
|
ReadBase128( FT_Stream stream,
|
|
|
|
FT_ULong* value )
|
|
|
|
{
|
|
|
|
FT_ULong result = 0;
|
|
|
|
FT_Int i;
|
|
|
|
FT_Byte code;
|
2019-06-16 15:26:18 +02:00
|
|
|
FT_Error error;
|
2019-06-11 20:51:20 +02:00
|
|
|
|
|
|
|
for ( i = 0; i < 5; ++i ) {
|
|
|
|
code = 0;
|
2019-06-16 15:26:18 +02:00
|
|
|
if( FT_READ_BYTE( code ) )
|
|
|
|
return FT_THROW( Invalid_Table );
|
2019-06-11 20:51:20 +02:00
|
|
|
|
|
|
|
/* Leading zeros are invalid. */
|
|
|
|
if ( i == 0 && code == 0x80 ) {
|
|
|
|
return FT_THROW( Invalid_Table );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If any of top seven bits are set then we're about to overflow. */
|
|
|
|
if ( result & 0xfe000000 ){
|
|
|
|
return FT_THROW( Invalid_Table );
|
|
|
|
}
|
|
|
|
|
|
|
|
result = ( result << 7 ) | ( code & 0x7f );
|
|
|
|
|
|
|
|
/* Spin until most significant bit of data byte is false. */
|
|
|
|
if ( (code & 0x80) == 0 ) {
|
|
|
|
*value = result;
|
|
|
|
return FT_Err_Ok;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Make sure not to exceed the size bound. */
|
|
|
|
return FT_THROW( Invalid_Table );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-16 15:26:18 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-30 01:01:04 +02:00
|
|
|
static FT_UInt64
|
2019-06-16 15:26:18 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-30 01:01:04 +02:00
|
|
|
static FT_Error
|
|
|
|
woff2_uncompress( FT_Byte* dst,
|
|
|
|
FT_ULong dst_size,
|
|
|
|
const FT_Byte* src,
|
|
|
|
FT_ULong src_size )
|
|
|
|
{
|
|
|
|
#ifdef FT_CONFIG_OPTION_USE_BROTLI
|
|
|
|
|
|
|
|
FT_ULong uncompressed_size = dst_size;
|
|
|
|
BrotliDecoderResult result;
|
|
|
|
|
|
|
|
result = BrotliDecoderDecompress(
|
|
|
|
src_size, src, &uncompressed_size, dst);
|
|
|
|
|
|
|
|
if( result != BROTLI_DECODER_RESULT_SUCCESS ||
|
|
|
|
uncompressed_size != dst_size )
|
2019-06-30 16:02:27 +02:00
|
|
|
{
|
|
|
|
FT_ERROR(( "woff2_uncompress: Stream length mismatch.\n" ));
|
2019-06-30 01:01:04 +02:00
|
|
|
return FT_THROW( Invalid_Table );
|
2019-06-30 16:02:27 +02:00
|
|
|
}
|
2019-06-30 01:01:04 +02:00
|
|
|
|
|
|
|
return FT_Err_Ok;
|
|
|
|
|
|
|
|
#else /* !FT_CONFIG_OPTION_USE_BROTLI */
|
|
|
|
|
|
|
|
FT_ERROR(( "woff2_uncompress: Brotli support not available.\n" ));
|
|
|
|
return FT_THROW( Unimplemented_Feature );
|
|
|
|
|
|
|
|
#endif /* !FT_CONFIG_OPTION_USE_BROTLI */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-11 20:51:20 +02:00
|
|
|
/* Replace `face->root.stream' with a stream containing the extracted */
|
|
|
|
/* SFNT of a WOFF2 font. */
|
|
|
|
|
|
|
|
FT_LOCAL_DEF( FT_Error )
|
|
|
|
woff2_open_font( FT_Stream stream,
|
2019-06-30 01:01:04 +02:00
|
|
|
TT_Face face,
|
|
|
|
FT_Int face_index )
|
2019-06-11 20:51:20 +02:00
|
|
|
{
|
|
|
|
FT_Memory memory = stream->memory;
|
|
|
|
FT_Error error = FT_Err_Ok;
|
|
|
|
|
|
|
|
WOFF2_HeaderRec woff2;
|
|
|
|
WOFF2_Table tables = NULL;
|
|
|
|
WOFF2_Table* indices = NULL;
|
2019-06-16 15:26:18 +02:00
|
|
|
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;
|
2019-06-11 20:51:20 +02:00
|
|
|
|
2019-06-23 21:37:18 +02:00
|
|
|
FT_Byte* sfnt = NULL;
|
|
|
|
FT_Stream sfnt_stream = NULL;
|
|
|
|
FT_Byte* sfnt_header;
|
|
|
|
|
2019-06-30 01:01:04 +02:00
|
|
|
FT_Byte* uncompressed_buf = NULL;
|
|
|
|
|
2019-06-11 20:51:20 +02:00
|
|
|
static const FT_Frame_Field woff2_header_fields[] =
|
|
|
|
{
|
|
|
|
#undef FT_STRUCTURE
|
|
|
|
#define FT_STRUCTURE WOFF2_HeaderRec
|
|
|
|
|
|
|
|
FT_FRAME_START( 48 ),
|
|
|
|
FT_FRAME_ULONG ( signature ),
|
|
|
|
FT_FRAME_ULONG ( flavor ),
|
|
|
|
FT_FRAME_ULONG ( length ),
|
|
|
|
FT_FRAME_USHORT( num_tables ),
|
|
|
|
FT_FRAME_SKIP_BYTES( 2 + 4 ),
|
|
|
|
FT_FRAME_ULONG ( totalCompressedSize ),
|
|
|
|
FT_FRAME_SKIP_BYTES( 2 * 2 ),
|
|
|
|
FT_FRAME_ULONG ( metaOffset ),
|
|
|
|
FT_FRAME_ULONG ( metaLength ),
|
|
|
|
FT_FRAME_ULONG ( metaOrigLength ),
|
|
|
|
FT_FRAME_ULONG ( privOffset ),
|
|
|
|
FT_FRAME_ULONG ( privLength ),
|
|
|
|
FT_FRAME_END
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* DEBUG - Remove later */
|
2019-06-16 15:26:18 +02:00
|
|
|
FT_TRACE2(( "woff2_open_font: Received Data.\n" ));
|
2019-06-11 20:51:20 +02:00
|
|
|
|
|
|
|
FT_ASSERT( stream == face->root.stream );
|
|
|
|
FT_ASSERT( FT_STREAM_POS() == 0 );
|
|
|
|
|
2019-06-23 21:37:18 +02:00
|
|
|
/* DEBUG - Remove later. */
|
2019-06-30 01:01:04 +02:00
|
|
|
FT_TRACE2(( "Face index = %ld\n", face_index ));
|
2019-06-23 21:37:18 +02:00
|
|
|
|
2019-06-11 20:51:20 +02:00
|
|
|
/* Read WOFF2 Header. */
|
|
|
|
if ( FT_STREAM_READ_FIELDS( woff2_header_fields, &woff2 ) )
|
|
|
|
return error;
|
|
|
|
|
|
|
|
/* DEBUG - Remove later. */
|
2019-06-16 15:26:18 +02:00
|
|
|
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 ));
|
2019-06-11 20:51:20 +02:00
|
|
|
|
|
|
|
/* Make sure we don't recurse back here. */
|
|
|
|
if ( woff2.flavor == TTAG_wOF2 )
|
|
|
|
return FT_THROW( Invalid_Table );
|
|
|
|
|
|
|
|
/* Miscellaneous checks. */
|
|
|
|
if ( woff2.length != stream->size ||
|
|
|
|
woff2.num_tables == 0 ||
|
|
|
|
48 + woff2.num_tables * 20UL >= woff2.length ||
|
|
|
|
( woff2.metaOffset == 0 && ( woff2.metaLength != 0 ||
|
|
|
|
woff2.metaOrigLength != 0 ) ) ||
|
|
|
|
( woff2.metaLength != 0 && woff2.metaOrigLength == 0 ) ||
|
|
|
|
( woff2.metaOffset >= woff2.length ) ||
|
|
|
|
( woff2.length - woff2.metaOffset < woff2.metaLength ) ||
|
|
|
|
( woff2.privOffset == 0 && woff2.privLength != 0 ) ||
|
|
|
|
( woff2.privOffset >= woff2.length ) ||
|
|
|
|
( woff2.length - woff2.privOffset < woff2.privLength ) )
|
|
|
|
{
|
2019-06-18 16:28:55 +02:00
|
|
|
FT_ERROR(( "woff2_font_open: invalid WOFF2 header\n" ));
|
2019-06-11 20:51:20 +02:00
|
|
|
return FT_THROW( Invalid_Table );
|
|
|
|
}
|
|
|
|
/* DEBUG - Remove later. */
|
|
|
|
else{
|
2019-06-16 15:26:18 +02:00
|
|
|
FT_TRACE2(( "WOFF2 Header is valid.\n" ));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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" ));
|
|
|
|
|
|
|
|
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;
|
2019-06-11 20:51:20 +02:00
|
|
|
}
|
|
|
|
|
2019-06-16 15:26:18 +02:00
|
|
|
/* 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
|
2019-06-30 01:01:04 +02:00
|
|
|
FT_TRACE2(( "glyf and loca indices are valid.\n" ));
|
2019-06-16 15:26:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/* 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 +
|
2019-06-30 16:02:27 +02:00
|
|
|
woff2.totalCompressedSize );
|
2019-06-16 15:26:18 +02:00
|
|
|
|
2019-06-30 01:01:04 +02:00
|
|
|
/* Few more checks before we start reading the tables. */
|
2019-06-16 15:26:18 +02:00
|
|
|
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 );
|
2019-06-11 20:51:20 +02:00
|
|
|
|
2019-06-30 01:01:04 +02:00
|
|
|
/* TODO Add support for uncompression of TTC fonts. */
|
2019-06-23 21:37:18 +02:00
|
|
|
/* Redirect a TTC to exit for now. */
|
|
|
|
if( woff2.header_version )
|
|
|
|
{
|
|
|
|
FT_TRACE2(( "Reading TTC fonts not supported yet.\n" ));
|
|
|
|
error = FT_THROW( Unimplemented_Feature );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write sfnt header. */
|
|
|
|
if ( FT_ALLOC( sfnt, 12 + woff2.num_tables * 16UL ) ||
|
|
|
|
FT_NEW( sfnt_stream ) )
|
|
|
|
goto Exit;
|
|
|
|
|
|
|
|
sfnt_header = sfnt;
|
|
|
|
|
|
|
|
{
|
|
|
|
FT_UInt searchRange, entrySelector, rangeShift, x;
|
|
|
|
/* DEBUG - Remove later */
|
|
|
|
FT_TRACE2(( "Writing SFNT offset table.\n" ));
|
|
|
|
|
|
|
|
x = woff2.num_tables;
|
|
|
|
entrySelector = 0;
|
|
|
|
while ( x )
|
|
|
|
{
|
|
|
|
x >>= 1;
|
|
|
|
entrySelector += 1;
|
|
|
|
}
|
|
|
|
entrySelector--;
|
|
|
|
|
|
|
|
searchRange = ( 1 << entrySelector ) * 16;
|
|
|
|
rangeShift = ( woff2.num_tables * 16 ) - searchRange;
|
|
|
|
|
|
|
|
WRITE_ULONG ( sfnt_header, woff2.flavor );
|
|
|
|
WRITE_USHORT( sfnt_header, woff2.num_tables );
|
|
|
|
WRITE_USHORT( sfnt_header, searchRange );
|
|
|
|
WRITE_USHORT( sfnt_header, entrySelector );
|
|
|
|
WRITE_USHORT( sfnt_header, rangeShift );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sort tables by tag. */
|
|
|
|
ft_qsort( indices,
|
|
|
|
woff2.num_tables,
|
|
|
|
sizeof ( WOFF2_Table ),
|
|
|
|
compare_tags );
|
|
|
|
|
|
|
|
/* DEBUG - Remove later */
|
|
|
|
FT_TRACE2(( "Sorted table indices: \n" ));
|
|
|
|
for( nn = 0; nn < woff2.num_tables; ++nn )
|
|
|
|
{
|
|
|
|
WOFF2_Table table = indices[nn];
|
|
|
|
/* DEBUG - Remove later */
|
|
|
|
FT_TRACE2(( " Index %d", nn ));
|
|
|
|
FT_TRACE2(( " %c%c%c%c\n",
|
|
|
|
(FT_Char)( table->Tag >> 24 ),
|
|
|
|
(FT_Char)( table->Tag >> 16 ),
|
|
|
|
(FT_Char)( table->Tag >> 8 ),
|
|
|
|
(FT_Char)( table->Tag )));
|
|
|
|
}
|
|
|
|
|
2019-06-30 01:01:04 +02:00
|
|
|
if( woff2.uncompressed_size < 1 )
|
|
|
|
{
|
|
|
|
error = FT_THROW( Invalid_Table );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate memory for uncompressed table data. */
|
|
|
|
if ( FT_ALLOC( uncompressed_buf, woff2.uncompressed_size ) ||
|
|
|
|
FT_FRAME_ENTER( woff2.totalCompressedSize ) )
|
|
|
|
goto Exit;
|
|
|
|
|
|
|
|
/* Uncompress the stream. */
|
|
|
|
error = woff2_uncompress( uncompressed_buf, woff2.uncompressed_size,
|
|
|
|
stream->cursor, woff2.totalCompressedSize );
|
|
|
|
if( error )
|
|
|
|
goto Exit;
|
|
|
|
|
|
|
|
FT_FRAME_EXIT();
|
|
|
|
|
|
|
|
/* TODO Write table entries. */
|
|
|
|
|
2019-06-11 20:51:20 +02:00
|
|
|
error = FT_THROW( Unimplemented_Feature );
|
2019-06-23 21:37:18 +02:00
|
|
|
/* DEBUG - Remove later */
|
2019-06-16 15:26:18 +02:00
|
|
|
FT_TRACE2(( "Reached end without errors.\n" ));
|
2019-06-11 20:51:20 +02:00
|
|
|
goto Exit;
|
|
|
|
|
|
|
|
Exit:
|
2019-06-23 21:37:18 +02:00
|
|
|
FT_FREE( tables );
|
|
|
|
FT_FREE( indices );
|
2019-06-30 16:02:27 +02:00
|
|
|
FT_FREE( uncompressed_buf );
|
2019-06-23 21:37:18 +02:00
|
|
|
|
|
|
|
if( error )
|
|
|
|
{
|
|
|
|
FT_FREE( sfnt );
|
|
|
|
FT_Stream_Close( sfnt_stream );
|
|
|
|
FT_FREE( sfnt_stream );
|
|
|
|
}
|
|
|
|
|
2019-06-11 20:51:20 +02:00
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-18 16:28:55 +02:00
|
|
|
#undef READ_255USHORT
|
|
|
|
#undef READ_BASE128
|
|
|
|
#undef ROUND4
|
2019-06-23 21:37:18 +02:00
|
|
|
#undef WRITE_USHORT
|
|
|
|
#undef WRITE_ULONG
|
2019-06-18 16:28:55 +02:00
|
|
|
|
|
|
|
|
2019-06-11 20:51:20 +02:00
|
|
|
/* END */
|