diff --git a/ChangeLog b/ChangeLog index 01bde59ab..f614d06d1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,43 @@ +2019-08-27 Nikhil Ramakrishnan + + [woff2] Reconstruct transformed `glyf' table. + + Reconstruct `glyf' table if it is transformed in the uncompressed + table stream. Also add necessary structures, macros and functions. + + * include/freetype/internal/wofftypes.h (WOFF2_InfoRec, + WOFF2_SubstreamRec, WOFF2_PointRec): New structures. + (WOFF2_TableRec): s/OrigLength/dst_length/. + + * src/sfnt/sfwoff2.c (READ_255USHORT, READ_BASE128): Use + `FT_SET_ERROR' to set implicit `error' variable. + + (WRITE_SHORT): New macro. + + (N_CONTOUR_STREAM, N_POINTS_STREAM, FLAG_STREAM, GLYPH_STREAM, + COMPOSITE_STREAM, BBOX_STREAM, INSTRUCTION_STREAM): New macros to + refer to substreams of the transformed `glyf' tables. + + (Read255UShort, ReadBase128): Return errors set by `FT_READ_XXX' + macros. + + (with_sign, safe_int_addition): New functions to add sign to values + based on a flag and perform safe addition respectively. + + (triplet_decode): Decode variable-length (flag, xCoordinate, + yCoordinate) triplet for a simple glyph. See + + https://www.w3.org/TR/WOFF2/#triplet_decoding + + (store_points, compute_bbox, composteGlyph_size, reconstruct_glyf): + New functions. + + (reconstruct_font): Call `reconstruct_glyf'. + + * src/sfnt/sfwoff2.h: Add required constants. + + * src/sfnt/woff2tags.h: Move out constants to `sfwoff2.h'. + 2019-08-27 Nikhil Ramakrishnan [woff2] Copy un-transformed tables to sfnt stream. diff --git a/include/freetype/internal/wofftypes.h b/include/freetype/internal/wofftypes.h index 205e64d7d..2035a251d 100644 --- a/include/freetype/internal/wofftypes.h +++ b/include/freetype/internal/wofftypes.h @@ -182,6 +182,34 @@ FT_BEGIN_HEADER } WOFF2_HeaderRec, *WOFF2_Header; + /************************************************************************** + * + * @struct: + * WOFF2_InfoRec + * + * @description: + * Metadata for WOFF2 font that may be required for reconstruction of + * sfnt tables. + * + * @fields: + * num_glyphs :: + * Number of glyphs in the font. + * + * num_hmetrics :: + * `numberOfHMetrics' field in the `hhea' table. + * + * x_mins :: + * `xMin' values of glyph bounding box. + */ + typedef struct WOFF2_InfoRec_ + { + FT_UShort num_glyphs; + FT_UShort num_hmetrics; + FT_Short* x_mins; + + } WOFF2_InfoRec, *WOFF2_Info; + + /************************************************************************** * * @struct: @@ -199,7 +227,7 @@ FT_BEGIN_HEADER { FT_Byte FlagByte; /* table type and flags */ FT_ULong Tag; /* table file offset */ - FT_ULong OrigLength; /* uncompressed table length */ + FT_ULong dst_length; /* uncompressed table length */ FT_ULong TransformLength; /* transformed length */ FT_ULong flags; /* calculated flags */ @@ -211,6 +239,60 @@ FT_BEGIN_HEADER } WOFF2_TableRec, *WOFF2_Table; + /************************************************************************** + * + * @struct: + * WOFF2_SubstreamRec + * + * @description: + * This structure stores information about a substream in the transformed + * `glyf' table in a WOFF2 stream. + * + * @fields: + * start :: + * Beginning of the substream relative to uncompressed table stream. + * + * offset :: + * Offset of the substream relative to uncompressed table stream. + * + * size :: + * Size of the substream. + */ + typedef struct WOFF2_SubstreamRec_ + { + FT_ULong start; + FT_ULong offset; + FT_ULong size; + } WOFF2_SubstreamRec, *WOFF2_Substream; + + + /************************************************************************** + * + * @struct: + * WOFF2_PointRec + * + * @description: + * This structure stores information about a point in the transformed + * `glyf' table in a WOFF2 stream. + * + * @fields: + * x :: + * x-coordinate. + * + * y :: + * y-coordinate. + * + * on_curve :: + * on-curve. + */ + typedef struct WOFF2_PointRec_ + { + FT_Int x; + FT_Int y; + FT_Bool on_curve; + } WOFF2_PointRec, *WOFF2_Point; + + FT_END_HEADER #endif /* WOFFTYPES_H_ */ diff --git a/src/sfnt/sfwoff2.c b/src/sfnt/sfwoff2.c index cc6a4d167..3a1baa127 100644 --- a/src/sfnt/sfwoff2.c +++ b/src/sfnt/sfwoff2.c @@ -40,9 +40,9 @@ #define FT_COMPONENT sfwoff2 -#define READ_255USHORT( var ) Read255UShort( stream, &var ) +#define READ_255USHORT( var ) FT_SET_ERROR( Read255UShort( stream, &var ) ) -#define READ_BASE128( var ) ReadBase128( stream, &var ) +#define READ_BASE128( var ) FT_SET_ERROR( ReadBase128( stream, &var ) ) #define ROUND4( var ) ( var + 3 ) & ~3 @@ -64,9 +64,26 @@ \ } while ( 0 ) +#define WRITE_SHORT( p, v ) \ + do \ + { \ + *(p)++ = ( (v) >> 8 ); \ + *(p)++ = ( (v) >> 0 ); \ + \ + } while ( 0 ) + #define WRITE_SFNT_BUF( buf, s ) \ write_buf( &sfnt, &dest_offset, buf, s, memory ) +#define N_CONTOUR_STREAM 0 +#define N_POINTS_STREAM 1 +#define FLAG_STREAM 2 +#define GLYPH_STREAM 3 +#define COMPOSITE_STREAM 4 +#define BBOX_STREAM 5 +#define INSTRUCTION_STREAM 6 + + static void stream_close( FT_Stream stream ) { @@ -110,32 +127,32 @@ static const FT_Int wordCode = 253; static const FT_Int lowestUCode = 253; - FT_Error error; + FT_Error error = FT_Err_Ok; FT_Byte code; - FT_Byte result_byte = 0; - FT_UShort result_short = 0; + FT_Byte result_byte = 0; + FT_UShort result_short = 0; if( FT_READ_BYTE( code ) ) - return FT_THROW( Invalid_Table ); + return error; if( code == wordCode ) { /* Read next two bytes and store FT_UShort value */ if( FT_READ_USHORT( result_short ) ) - return FT_THROW( Invalid_Table ); + return error; *value = result_short; return FT_Err_Ok; } else if( code == oneMoreByteCode1 ) { if( FT_READ_BYTE( result_byte ) ) - return FT_THROW( Invalid_Table ); + return error; *value = result_byte + lowestUCode; return FT_Err_Ok; } else if( code == oneMoreByteCode2 ) { if( FT_READ_BYTE( result_byte ) ) - return FT_THROW( Invalid_Table ); + return error; *value = result_byte + lowestUCode * 2; return FT_Err_Ok; } @@ -154,27 +171,26 @@ FT_ULong result = 0; FT_Int i; FT_Byte code; - FT_Error error; + FT_Error error = FT_Err_Ok; for ( i = 0; i < 5; ++i ) { code = 0; if( FT_READ_BYTE( code ) ) - return FT_THROW( Invalid_Table ); + return error; /* Leading zeros are invalid. */ - if ( i == 0 && code == 0x80 ) { + 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 ){ + 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 ) { + if ( ( code & 0x80 ) == 0 ) + { *value = result; return FT_Err_Ok; } @@ -200,11 +216,11 @@ return FT_THROW( Array_Too_Large ); /* DEBUG - Remove later */ - FT_TRACE2(( "Reallocating %lu to %lu.\n", *offset, (*offset + size) )); + /* FT_TRACE2(( "Reallocating %lu to %lu.\n", *offset, (*offset + size) )); */ /* Reallocate `dst'. */ if ( FT_REALLOC( dst, - (FT_ULong)(*offset), - (FT_ULong)(*offset + size ) ) ) + (FT_ULong)( *offset ), + (FT_ULong)( *offset + size ) ) ) goto Exit; /* Copy data. */ @@ -253,6 +269,7 @@ return offset; } + static FT_Long compute_ULong_sum( FT_Byte* buf, FT_ULong size ) @@ -268,7 +285,7 @@ ( buf[i+2] << 8 ) | ( buf[i+3] << 0 ); } - /* If size is not aligned to 4, treat as if it is padded with 0s */ + /* If size is not aligned to 4, treat as if it is padded with 0s. */ if( size != aligned_size ) { v = 0; @@ -345,23 +362,743 @@ *num_hmetrics = num_metrics; + /* DEBUG - Remove later */ FT_TRACE2(( "num_hmetrics = %d\n", *num_hmetrics )); return error; } - static FT_Error - reconstruct_font( FT_Byte* transformed_buf, - FT_ULong transformed_buf_size, - WOFF2_Table* indices, - WOFF2_Header woff2, - FT_Byte** sfnt_bytes, - FT_Memory memory ) + static FT_Int + with_sign( FT_Byte flag, + FT_Int base_val ) { - FT_Error error = FT_Err_Ok; - FT_Stream stream = NULL; - FT_Byte* buf_cursor = NULL; + /* Precondition: 0 <= base_val < 65536 (to avoid overflow). */ + return ( flag & 1 ) ? base_val : -base_val; + } + + + static FT_Int + safe_int_addition( FT_Int a, + FT_Int b, + FT_Int* result ) + { + if( ( ( a > 0 ) && ( b > FT_INT_MAX - a ) ) || + ( ( a < 0 ) && ( b < FT_INT_MIN - a ) ) ) + return FT_THROW( Invalid_Table ); + + *result = a + b; + return FT_Err_Ok; + } + + + static FT_Error + triplet_decode( const FT_Byte* flags_in, + const FT_Byte* in, + FT_ULong in_size, + FT_ULong n_points, + WOFF2_Point result, + FT_ULong* in_bytes_used ) + { + FT_Int x = 0; + FT_Int y = 0; + FT_Int dx; + FT_Int dy; + FT_Int b0, b1, b2; + + FT_ULong triplet_index = 0; + FT_ULong data_bytes; + + FT_Int i; + + if ( n_points > in_size ) + return FT_THROW( Invalid_Table ); + + for ( i = 0; i < n_points; ++i ) + { + FT_Byte flag = flags_in[i]; + FT_Bool on_curve = !( flag >> 7 ); + flag &= 0x7f; + if( flag < 84 ) + data_bytes = 1; + else if( flag < 120 ) + data_bytes = 2; + else if( flag < 124 ) + data_bytes = 3; + else + data_bytes = 4; + + /* Overflow checks */ + if ( triplet_index + data_bytes > in_size || + triplet_index + data_bytes < triplet_index ) + return FT_THROW( Invalid_Table ); + + if ( flag < 10 ) + { + dx = 0; + dy = with_sign( flag, + ( ( flag & 14 ) << 7 ) + in[triplet_index] ); + } + else if ( flag < 20 ) + { + dx = with_sign( flag, + ( ( ( flag - 10 ) & 14 ) << 7 ) + in[triplet_index] ); + dy = 0; + } + else if ( flag < 84 ) + { + b0 = flag - 20; + b1 = in[triplet_index]; + dx = with_sign( flag, + 1 + ( b0 & 0x30 ) + ( b1 >> 4 ) ); + dy = with_sign( flag >> 1, + 1 + ( ( b0 & 0x0c ) << 2 ) + ( b1 & 0x0f ) ); + } + else if ( flag < 120 ) + { + b0 = flag - 84; + dx = with_sign( flag, + 1 + ( ( b0 / 12 ) << 8 ) + in[triplet_index] ); + dy = with_sign( flag >> 1, + 1 + ( ( ( b0 % 12 ) >> 2 ) << 8 ) + + in[triplet_index + 1] ); + } + else if ( flag < 124 ) + { + b2 = in[triplet_index + 1]; + dx = with_sign( flag, + ( in[triplet_index] << 4 ) + ( b2 >> 4 ) ); + dy = with_sign( flag >> 1, + ( ( b2 & 0x0f ) << 8 ) + in[triplet_index + 2] ); + } + else + { + dx = with_sign( flag, + ( in[triplet_index] << 8 ) + + in[triplet_index + 1] ); + dy = with_sign( flag >> 1, + ( in[triplet_index + 2] << 8 ) + + in[triplet_index + 3] ); + } + triplet_index += data_bytes; + + if ( safe_int_addition( x, dx, &x ) ) + return FT_THROW( Invalid_Table ); + + if ( safe_int_addition( y, dy, &y ) ) + return FT_THROW( Invalid_Table ); + + result[i].x = x; + result[i].y = y; + result[i].on_curve = on_curve; + } + *in_bytes_used = triplet_index; + return FT_Err_Ok; + } + + + static FT_Error + store_points( FT_ULong n_points, + const WOFF2_Point points, + FT_UShort n_contours, + FT_UShort instruction_len, + FT_Byte* dst, + FT_ULong dst_size, + FT_ULong* glyph_size ) + { + FT_UInt flag_offset = 10 + ( 2 * n_contours ) + 2 + instruction_len; + FT_Int last_flag = -1; + FT_Int repeat_count = 0; + FT_Int last_x = 0; + FT_Int last_y = 0; + FT_UInt x_bytes = 0; + FT_UInt y_bytes = 0; + FT_UInt xy_bytes; + FT_UInt i; + FT_UInt x_offset; + FT_UInt y_offset; + + FT_Byte* pointer; + + for ( i = 0; i < n_points; ++i ) + { + const WOFF2_PointRec point = points[i]; + + FT_Int flag = point.on_curve ? GLYF_ON_CURVE : 0; + FT_Int dx = point.x - last_x; + FT_Int dy = point.y - last_y; + + if ( dx == 0 ) + flag |= GLYF_THIS_X_IS_SAME; + else if ( dx > -256 && dx < 256 ) + { + flag |= GLYF_X_SHORT | ( dx > 0 ? GLYF_THIS_X_IS_SAME : 0 ); + x_bytes += 1; + } + else + x_bytes += 2; + + if ( dy == 0 ) + flag |= GLYF_THIS_Y_IS_SAME; + else if ( dy > -256 && dy < 256 ) + { + flag |= GLYF_Y_SHORT | ( dy > 0 ? GLYF_THIS_Y_IS_SAME : 0 ); + y_bytes += 1; + } + else + y_bytes += 2; + + if ( flag == last_flag && repeat_count != 255 ) + { + dst[flag_offset - 1] |= GLYF_REPEAT; + repeat_count++; + } + else + { + if ( repeat_count != 0 ) + { + if ( flag_offset >= dst_size ) + return FT_THROW( Invalid_Table ); + + dst[flag_offset++] = repeat_count; + } + if ( flag_offset >= dst_size ) + return FT_THROW( Invalid_Table ); + + dst[flag_offset++] = flag; + repeat_count = 0; + } + last_x = point.x; + last_y = point.y; + last_flag = flag; + } + + if ( repeat_count != 0 ) + { + if ( flag_offset >= dst_size ) + return FT_THROW( Invalid_Table ); + + dst[flag_offset++] = repeat_count; + } + + xy_bytes = x_bytes + y_bytes; + if ( xy_bytes < x_bytes || + flag_offset + xy_bytes < flag_offset || + flag_offset + xy_bytes > dst_size ) + return FT_THROW( Invalid_Table ); + + x_offset = flag_offset; + y_offset = flag_offset + x_bytes; + last_x = 0; + last_y = 0; + for ( i = 0; i < n_points; ++i ) + { + FT_Int dx = points[i].x - last_x; + FT_Int dy = points[i].y - last_y; + + if ( dx == 0 ) {;} + else if ( dx > -256 && dx < 256 ) + dst[x_offset++] = FT_ABS( dx ); + else + { + pointer = dst + x_offset; + WRITE_SHORT( pointer, dx ); + x_offset += 2; + } + + last_x += dx; + + if ( dy == 0 ) {;} + else if ( dy > -256 && dy < 256 ) + dst[y_offset++] = FT_ABS( dy ); + else + { + pointer = dst + y_offset; + WRITE_SHORT( pointer, dy ); + y_offset += 2; + } + + last_y += dy; + } + + *glyph_size = y_offset; + return FT_Err_Ok; + } + + + static void + compute_bbox( FT_ULong n_points, + const WOFF2_Point points, + FT_Byte* dst, + FT_UShort* src_x_min ) + { + FT_Int x_min = 0; + FT_Int y_min = 0; + FT_Int x_max = 0; + FT_Int y_max = 0; + FT_Int i; + + FT_ULong offset; + FT_Byte* pointer; + + if ( n_points > 0 ) + { + x_min = points[0].x; + y_min = points[0].y; + x_max = points[0].x; + y_max = points[0].y; + } + for ( i = 1; i < n_points; ++i ) + { + FT_Int x = points[i].x; + FT_Int y = points[i].y; + + x_min = FT_MIN( x, x_min ); + y_min = FT_MIN( y, y_min ); + x_max = FT_MAX( x, x_max ); + y_max = FT_MAX( y, y_max ); + } + + /* Write values to `glyf' record. */ + offset = 2; + pointer = dst + offset; + WRITE_SHORT( pointer, x_min ); + WRITE_SHORT( pointer, y_min ); + WRITE_SHORT( pointer, x_max ); + WRITE_SHORT( pointer, y_max ); + + *src_x_min = (FT_UShort)x_min; + } + + + static FT_Error + compositeGlyph_size( FT_Stream stream, + FT_ULong offset, + FT_ULong* size, + FT_Bool* have_instructions ) + { + FT_Error error = FT_Err_Ok; + FT_ULong start_offset = offset; + FT_Bool we_have_inst = FALSE; + FT_UShort flags = FLAG_MORE_COMPONENTS; + + if( FT_STREAM_SEEK( start_offset ) ) + goto Exit; + while ( flags & FLAG_MORE_COMPONENTS ) + { + FT_ULong arg_size; + + if ( FT_READ_USHORT( flags ) ) + goto Exit; + we_have_inst |= ( flags & FLAG_WE_HAVE_INSTRUCTIONS ) != 0; + /* glyph index */ + arg_size = 2; + if ( flags & FLAG_ARG_1_AND_2_ARE_WORDS ) + arg_size += 4; + else + arg_size += 2; + + if ( flags & FLAG_WE_HAVE_A_SCALE ) + arg_size += 2; + else if ( flags & FLAG_WE_HAVE_AN_X_AND_Y_SCALE ) + arg_size += 4; + else if ( flags & FLAG_WE_HAVE_A_TWO_BY_TWO ) + arg_size += 8; + + if( FT_STREAM_SKIP( arg_size ) ) + goto Exit; + } + + *size = FT_STREAM_POS() - start_offset; + *have_instructions = we_have_inst; + + Exit: + return error; + } + + + static FT_Error + reconstruct_glyf( FT_Stream stream, + WOFF2_Table glyf_table, + FT_ULong* glyf_checksum, + WOFF2_Table loca_table, + FT_ULong* loca_checksum, + FT_Byte** sfnt_bytes, + FT_ULong* out_offset, + WOFF2_Info info, + FT_Memory memory ) + { + FT_Error error = FT_Err_Ok; + FT_Byte* sfnt = *sfnt_bytes; + + /* Current position in stream */ + const FT_ULong pos = FT_STREAM_POS(); + + FT_UInt num_substreams = 7; + + FT_UShort num_glyphs; + FT_UShort index_format; + FT_ULong expected_loca_length; + FT_UInt offset; + FT_Int i; + FT_ULong points_size; + FT_ULong bitmap_length; + FT_ULong glyph_buf_size; + FT_ULong bbox_bitmap_offset; + + const FT_ULong glyf_start = *out_offset; + FT_ULong dest_offset = *out_offset; + + WOFF2_Substream substreams = NULL; + + FT_ULong* loca_values = NULL; + FT_UShort* n_points_arr = NULL; + FT_Byte* glyph_buf = NULL; + WOFF2_Point points = NULL; + + if( FT_NEW_ARRAY( substreams, num_substreams ) ) + goto Fail; + + if( FT_STREAM_SKIP( 4 ) ) + goto Fail; + if( FT_READ_USHORT( num_glyphs ) ) + goto Fail; + if( FT_READ_USHORT( index_format ) ) + goto Fail; + + FT_TRACE2(( "Num_glyphs = %u; index_format = %u\n", num_glyphs, index_format )); + + /* Calculate expected length of loca and compare. */ + /* See https://www.w3.org/TR/WOFF2/#conform-mustRejectLoca */ + /* index_format = 0 => Short version `loca'. */ + /* index_format = 1 => Long version `loca'. */ + expected_loca_length = ( index_format ? 4 : 2 ) * + ( (FT_ULong)num_glyphs + 1 ); + if( loca_table->dst_length != expected_loca_length ) + goto Fail; + + offset = ( 2 + num_substreams ) * 4; + if( offset > glyf_table->TransformLength ) + goto Fail; + + for ( i = 0; i < num_substreams; ++i ) + { + FT_ULong substream_size; + if( FT_READ_ULONG( substream_size ) ) + goto Fail; + if( substream_size > glyf_table->TransformLength - offset ) + goto Fail; + + substreams[i].start = pos + offset; + substreams[i].offset = pos + offset; + substreams[i].size = substream_size; + + /* DEBUG - Remove later */ + FT_TRACE2(( "Substream %d: offset = %lu; size = %lu;\n", + i, substreams[i].offset, substreams[i].size )); + offset += substream_size; + } + + if( FT_NEW_ARRAY( loca_values, num_glyphs + 1 ) ) + goto Fail; + + points_size = 0; + bbox_bitmap_offset = substreams[BBOX_STREAM].offset; + + /* Size of bboxBitmap = 4 * floor((numGlyphs + 31) / 32) */ + bitmap_length = ( ( num_glyphs + 31 ) >> 5 ) << 2; + substreams[BBOX_STREAM].offset += bitmap_length; + + glyph_buf_size = WOFF2_DEFAULT_GLYPH_BUF; + if( FT_NEW_ARRAY( glyph_buf, glyph_buf_size ) ) + goto Fail; + + if( FT_NEW_ARRAY( info->x_mins, num_glyphs ) ) + goto Fail; + + for ( i = 0; i < num_glyphs; ++i ) + { + FT_ULong glyph_size = 0; + FT_UShort n_contours = 0; + FT_Bool have_bbox = FALSE; + FT_Byte bbox_bitmap; + FT_ULong bbox_offset; + FT_UShort x_min; + + /* Set `have_bbox'. */ + bbox_offset = bbox_bitmap_offset + ( i >> 3 ); + if( FT_STREAM_SEEK( bbox_offset ) || + FT_READ_BYTE( bbox_bitmap ) ) + goto Fail; + if( bbox_bitmap & ( 0x80 >> ( i & 7 ) ) ) + have_bbox = TRUE; + + /* Read value from `nContourStream' */ + if( FT_STREAM_SEEK( substreams[N_CONTOUR_STREAM].offset ) || + FT_READ_USHORT( n_contours ) ) + goto Fail; + substreams[N_CONTOUR_STREAM].offset += 2; + + if( n_contours == 0xffff ) + { + /* Composite glyph */ + FT_Bool have_instructions = FALSE; + FT_UShort instruction_size = 0; + FT_ULong composite_size; + FT_ULong size_needed; + FT_Byte* pointer = NULL; + + /* Composite glyphs must have explicit bbox. */ + if( !have_bbox ) + goto Fail; + + if( compositeGlyph_size( stream, + substreams[COMPOSITE_STREAM].offset, + &composite_size, + &have_instructions) ) + goto Fail; + + if( have_instructions ) + { + if( FT_STREAM_SEEK( substreams[GLYPH_STREAM].offset ) || + READ_255USHORT( instruction_size ) ) + goto Fail; + substreams[GLYPH_STREAM].offset = FT_STREAM_POS(); + } + + size_needed = 12 + composite_size + instruction_size; + if( glyph_buf_size < size_needed ) + { + if( FT_RENEW_ARRAY( glyph_buf, glyph_buf_size, size_needed ) ) + goto Fail; + glyph_buf_size = size_needed; + } + + pointer = glyph_buf + glyph_size; + WRITE_USHORT( pointer, n_contours ); + glyph_size += 2; + + /* Read x_min for current glyph. */ + if( FT_STREAM_SEEK( substreams[BBOX_STREAM].offset ) || + FT_READ_USHORT( x_min ) ) + goto Fail; + /* No increment here because we read again. */ + + if( FT_STREAM_SEEK( substreams[BBOX_STREAM].offset ) || + FT_STREAM_READ( glyph_buf + glyph_size, 8 ) ) + goto Fail; + substreams[BBOX_STREAM].offset += 8; + glyph_size += 8; + + if( FT_STREAM_SEEK( substreams[COMPOSITE_STREAM].offset ) || + FT_STREAM_READ( glyph_buf + glyph_size, composite_size ) ) + goto Fail; + substreams[COMPOSITE_STREAM].offset += composite_size; + glyph_size += composite_size; + + if( have_instructions ) + { + pointer = glyph_buf + glyph_size; + WRITE_USHORT( pointer, instruction_size ); + glyph_size += 2; + if( FT_STREAM_SEEK( substreams[INSTRUCTION_STREAM].offset ) || + FT_STREAM_READ( glyph_buf + glyph_size, instruction_size ) ) + goto Fail; + substreams[INSTRUCTION_STREAM].offset += instruction_size; + glyph_size += instruction_size; + } + } + else if( n_contours > 0 ) + { + /* Simple glyph */ + FT_ULong total_n_points = 0; + FT_UShort n_points_contour; + FT_UInt j; + FT_ULong flag_size; + FT_ULong triplet_size; + FT_ULong triplet_bytes_used; + FT_Byte* flags_buf; + FT_Byte* triplet_buf; + FT_UShort instruction_size; + FT_ULong size_needed; + FT_Int end_point; + FT_UInt contour_ix; + + FT_Byte* pointer = NULL; + + if( FT_NEW_ARRAY( n_points_arr, n_contours ) ) + goto Fail; + + if( FT_STREAM_SEEK( substreams[N_POINTS_STREAM].offset ) ) + goto Fail; + for( j = 0; j < n_contours; ++j ) + { + if( READ_255USHORT( n_points_contour ) ) + goto Fail; + n_points_arr[j] = n_points_contour; + /* Prevent negative/overflow. */ + if( total_n_points + n_points_contour < total_n_points ) + goto Fail; + total_n_points += n_points_contour; + } + substreams[N_POINTS_STREAM].offset = FT_STREAM_POS(); + + flag_size = total_n_points; + if( flag_size > substreams[FLAG_STREAM].size ) + goto Fail; + + flags_buf = stream->base + substreams[FLAG_STREAM].offset; + triplet_buf = stream->base + substreams[GLYPH_STREAM].offset; + + triplet_size = substreams[GLYPH_STREAM].size - + ( substreams[GLYPH_STREAM].offset - + substreams[GLYPH_STREAM].start ); + triplet_bytes_used = 0; + /* Create array to store point information. */ + points_size = total_n_points; + if( FT_NEW_ARRAY( points, points_size ) ) + goto Fail; + + if( triplet_decode( flags_buf, triplet_buf, + triplet_size, total_n_points, + points, &triplet_bytes_used ) ) + goto Fail; + substreams[FLAG_STREAM].offset += flag_size; + substreams[GLYPH_STREAM].offset += triplet_bytes_used; + + if( FT_STREAM_SEEK( substreams[GLYPH_STREAM].offset ) || + READ_255USHORT( instruction_size ) ) + goto Fail; + substreams[GLYPH_STREAM].offset = FT_STREAM_POS(); + + if( total_n_points >= ( 1 << 27 ) || + instruction_size >= ( 1 << 30 ) ) + goto Fail; + size_needed = 12 + ( 2 * n_contours ) + ( 5 * total_n_points ) + + instruction_size; + if( glyph_buf_size < size_needed ) + { + if( FT_RENEW_ARRAY( glyph_buf, glyph_buf_size, size_needed ) ) + goto Fail; + glyph_buf_size = size_needed; + } + + pointer = glyph_buf + glyph_size; + WRITE_USHORT( pointer, n_contours ); + glyph_size += 2; + + if( have_bbox ) + { + /* Read x_min for current glyph. */ + if( FT_STREAM_SEEK( substreams[BBOX_STREAM].offset ) || + FT_READ_USHORT( x_min ) ) + goto Fail; + /* No increment here because we read again. */ + + if( FT_STREAM_SEEK( substreams[BBOX_STREAM].offset ) || + FT_STREAM_READ( glyph_buf + glyph_size, 8 ) ) + goto Fail; + substreams[BBOX_STREAM].offset += 8; + } + else + compute_bbox( total_n_points, points, glyph_buf, &x_min ); + + glyph_size = CONTOUR_OFFSET_END_POINT; + pointer = glyph_buf + glyph_size; + end_point = -1; + for( contour_ix = 0; contour_ix < n_contours; ++contour_ix ) + { + end_point += n_points_arr[contour_ix]; + if( end_point >= 65536 ) + goto Fail; + + WRITE_SHORT( pointer, end_point ); + glyph_size += 2; + } + + WRITE_USHORT( pointer, instruction_size ); + glyph_size += 2; + if( FT_STREAM_SEEK( substreams[INSTRUCTION_STREAM].offset ) || + FT_STREAM_READ( glyph_buf + glyph_size, instruction_size ) ) + goto Fail; + glyph_size += instruction_size; + + if( store_points( total_n_points, points, n_contours, + instruction_size, glyph_buf, + glyph_buf_size, &glyph_size ) ) + goto Fail; + + FT_FREE( points ); + FT_FREE( n_points_arr ); + } + else + { + /* Empty glyph. */ + /* Must not have a bbox. */ + if( have_bbox ) + { + FT_ERROR(( "Empty glyph has a bbox.\n" )); + goto Fail; + } + } + loca_values[i] = dest_offset - glyf_start; + if( WRITE_SFNT_BUF( glyph_buf, glyph_size ) ) + goto Fail; + + *glyf_checksum += compute_ULong_sum( glyph_buf, glyph_size ); + + /* Store x_mins, may be required to reconstruct `hmtx'. */ + if ( n_contours > 0 ) + info->x_mins[i] = x_min; + } + + glyf_table->dst_length = dest_offset - glyf_table->dst_offset; + + + /* TODO Reconstruct `loca' table and set its `dst_length'. */ + + /* Set pointer of `sfnt_bytes' to its correct value. */ + *sfnt_bytes = sfnt; + *out_offset = dest_offset; + /* DEBUG - Remove later */ + if( !error ) + FT_TRACE2(( "reconstruct_glyf proceeding w/o errors.\n" )); + + FT_FREE( substreams ); + FT_FREE( loca_values ); + FT_FREE( n_points_arr ); + FT_FREE( glyph_buf ); + FT_FREE( points ); + + return error; + + Fail: + if( !error ) + error = FT_THROW( Invalid_Table ); + + FT_FREE( substreams ); + FT_FREE( loca_values ); + FT_FREE( n_points_arr ); + FT_FREE( glyph_buf ); + FT_FREE( points ); + + return error; + } + + + static FT_Error + reconstruct_font( FT_Byte* transformed_buf, + FT_ULong transformed_buf_size, + WOFF2_Table* indices, + WOFF2_Header woff2, + WOFF2_Info info, + FT_Byte** sfnt_bytes, + FT_Memory memory ) + { + FT_Error error = FT_Err_Ok; + FT_Stream stream = NULL; + FT_Byte* buf_cursor = NULL; + /* We are reallocating memory for `sfnt', so its pointer may change. */ FT_Byte* sfnt = *sfnt_bytes; @@ -398,9 +1135,6 @@ } } - FT_UNUSED( loca_checksum ); - FT_UNUSED( checksum ); - /* Create a stream for the uncompressed buffer. */ if ( FT_NEW( stream ) ) return FT_THROW( Invalid_Table ); @@ -436,6 +1170,8 @@ return FT_THROW( Invalid_Table ); } + info->num_hmetrics = num_hmetrics; + checksum = 0; if( ( table.flags & WOFF2_FLAGS_TRANSFORM ) != WOFF2_FLAGS_TRANSFORM ) { @@ -461,14 +1197,26 @@ /* DEBUG - Remove later */ FT_TRACE2(( "This table has xform.\n" )); - /* TODO reconstruct transformed font tables! */ - } + if( table.Tag == TTAG_glyf ) + { + table.dst_offset = dest_offset; + if( reconstruct_glyf( stream, &table, &checksum, + loca_table, &loca_checksum, + &sfnt, &dest_offset, info, + memory ) ) + return FT_THROW( Invalid_Table ); + } + + /* TODO reconstruct transformed loca and hmtx! */ + } } /* Set pointer of sfnt stream to its correct value. */ *sfnt_bytes = sfnt; return FT_Err_Ok; + + /* TODO delete the uncompressed stream after everything is done. */ } @@ -484,6 +1232,7 @@ FT_Error error = FT_Err_Ok; WOFF2_HeaderRec woff2; + WOFF2_InfoRec info; WOFF2_Table tables = NULL; WOFF2_Table* indices = NULL; WOFF2_Table* temp_indices = NULL; @@ -613,10 +1362,10 @@ flags |= xform_version; - if( READ_BASE128( table->OrigLength ) ) + if( READ_BASE128( table->dst_length ) ) goto Exit; - table->TransformLength = table->OrigLength; + table->TransformLength = table->dst_length; if ( ( flags & WOFF2_FLAGS_TRANSFORM ) != 0 ) { @@ -647,7 +1396,7 @@ (FT_Char)( table->Tag ), table->FlagByte & 0x3f, ( table->FlagByte >> 6 ) & 0x03, - table->OrigLength, + table->dst_length, table->TransformLength, table->src_length, table->src_offset )); @@ -876,9 +1625,10 @@ FT_FRAME_EXIT(); - /* TODO Write table entries. */ reconstruct_font( uncompressed_buf, woff2.uncompressed_size, - indices, &woff2, &sfnt, memory ); + indices, &woff2, &info, &sfnt, memory ); + + /* TODO Write table entries. */ error = FT_THROW( Unimplemented_Feature ); /* DEBUG - Remove later */ @@ -906,7 +1656,16 @@ #undef ROUND4 #undef WRITE_USHORT #undef WRITE_ULONG +#undef WRITE_SHORT #undef WRITE_SFNT_BUF +#undef N_CONTOUR_STREAM +#undef N_POINTS_STREAM +#undef FLAG_STREAM +#undef GLYPH_STREAM +#undef COMPOSITE_STREAM +#undef BBOX_STREAM +#undef INSTRUCTION_STREAM + /* END */ diff --git a/src/sfnt/sfwoff2.h b/src/sfnt/sfwoff2.h index f7e7f43f6..0f8d4697c 100644 --- a/src/sfnt/sfwoff2.h +++ b/src/sfnt/sfwoff2.h @@ -28,6 +28,39 @@ FT_BEGIN_HEADER + /* Leave the first byte open to store flag_byte. */ +#define WOFF2_FLAGS_TRANSFORM 1 << 8 + +#define WOFF2_SFNT_HEADER_SIZE 12 +#define WOFF2_SFNT_ENTRY_SIZE 16 + + /* Suggested max size for output. */ +#define WOFF2_DEFAULT_MAX_SIZE 30 * 1024 * 1024 + + /* 98% of Google Fonts have no glyph above 5k bytes. */ +#define WOFF2_DEFAULT_GLYPH_BUF 5120 + + /* Composite glyph flags */ + /* See CompositeGlyph.java in `sfntly' for full definitions */ +#define FLAG_ARG_1_AND_2_ARE_WORDS 1 << 0 +#define FLAG_WE_HAVE_A_SCALE 1 << 3 +#define FLAG_MORE_COMPONENTS 1 << 5 +#define FLAG_WE_HAVE_AN_X_AND_Y_SCALE 1 << 6 +#define FLAG_WE_HAVE_A_TWO_BY_TWO 1 << 7 +#define FLAG_WE_HAVE_INSTRUCTIONS 1 << 8 + + /* Simple glyph flags */ +#define GLYF_ON_CURVE 1 << 0 +#define GLYF_X_SHORT 1 << 1 +#define GLYF_Y_SHORT 1 << 2 +#define GLYF_REPEAT 1 << 3 +#define GLYF_THIS_X_IS_SAME 1 << 4 +#define GLYF_THIS_Y_IS_SAME 1 << 5 + + /* Other constants */ +#define CONTOUR_OFFSET_END_POINT 10 + + FT_LOCAL( FT_Error ) woff2_open_font( FT_Stream stream, TT_Face face, diff --git a/src/sfnt/woff2tags.h b/src/sfnt/woff2tags.h index 422567dac..6cd87bfe4 100644 --- a/src/sfnt/woff2tags.h +++ b/src/sfnt/woff2tags.h @@ -27,15 +27,6 @@ FT_BEGIN_HEADER - /* Leave the first byte open to store flag_byte. */ -#define WOFF2_FLAGS_TRANSFORM 1 << 8 - -#define WOFF2_SFNT_HEADER_SIZE 12 -#define WOFF2_SFNT_ENTRY_SIZE 16 - - /* Suggested max size for output. */ -#define WOFF2_DEFAULT_MAX_SIZE 30 * 1024 * 1024 - FT_LOCAL( FT_ULong ) woff2_known_tags( FT_Byte index );