| /**************************************************************************** |
| * |
| * sfwoff2.c |
| * |
| * WOFF2 format management (base). |
| * |
| * Copyright (C) 2019-2024 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 "sfwoff2.h" |
| #include "woff2tags.h" |
| #include <freetype/tttags.h> |
| #include <freetype/internal/ftcalc.h> |
| #include <freetype/internal/ftdebug.h> |
| #include <freetype/internal/ftstream.h> |
| |
| |
| #ifdef FT_CONFIG_OPTION_USE_BROTLI |
| |
| #include <brotli/decode.h> |
| |
| |
| /************************************************************************** |
| * |
| * 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 |
| |
| /* An arbitrary, heuristic size limit (67MByte) for expanded WOFF2 data. */ |
| #define MAX_SFNT_SIZE ( 1 << 26 ) |
| |
| #define READ_255USHORT( var ) FT_SET_ERROR( Read255UShort( stream, &var ) ) |
| |
| #define READ_BASE128( var ) FT_SET_ERROR( ReadBase128( stream, &var ) ) |
| |
| /* `var' should be FT_ULong */ |
| #define ROUND4( var ) ( ( var + 3 ) & ~3UL ) |
| |
| #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 ) |
| |
| #define WRITE_SHORT( p, v ) \ |
| do \ |
| { \ |
| *(p)++ = (FT_Byte)( (v) >> 8 ); \ |
| *(p)++ = (FT_Byte)( (v) >> 0 ); \ |
| \ |
| } while ( 0 ) |
| |
| #define WRITE_SFNT_BUF( buf, s ) \ |
| write_buf( &sfnt, sfnt_size, &dest_offset, buf, s, memory ) |
| |
| #define WRITE_SFNT_BUF_AT( offset, buf, s ) \ |
| write_buf( &sfnt, sfnt_size, &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 |
| |
| #define HAVE_OVERLAP_SIMPLE_BITMAP 0x1 |
| |
| |
| static void |
| stream_close( FT_Stream stream ) |
| { |
| FT_Memory memory = stream->memory; |
| |
| |
| FT_FREE( stream->base ); |
| |
| stream->size = 0; |
| stream->close = NULL; |
| } |
| |
| |
| FT_COMPARE_DEF( int ) |
| compare_tags( const void* a, |
| const void* b ) |
| { |
| WOFF2_Table table1 = *(WOFF2_Table*)a; |
| WOFF2_Table table2 = *(WOFF2_Table*)b; |
| |
| FT_Tag tag1 = table1->Tag; |
| FT_Tag tag2 = table2->Tag; |
| |
| |
| if ( tag1 > tag2 ) |
| return 1; |
| else if ( tag1 < tag2 ) |
| return -1; |
| else |
| return 0; |
| } |
| |
| |
| static FT_Error |
| Read255UShort( FT_Stream stream, |
| FT_UShort* value ) |
| { |
| const FT_Byte oneMoreByteCode1 = 255; |
| const FT_Byte oneMoreByteCode2 = 254; |
| const FT_Byte wordCode = 253; |
| const FT_UShort lowestUCode = 253; |
| |
| FT_Error error = FT_Err_Ok; |
| FT_Byte code; |
| FT_Byte result_byte = 0; |
| FT_UShort result_short = 0; |
| |
| |
| if ( FT_READ_BYTE( code ) ) |
| return error; |
| if ( code == wordCode ) |
| { |
| /* Read next two bytes and store `FT_UShort' value. */ |
| if ( FT_READ_USHORT( result_short ) ) |
| return error; |
| *value = result_short; |
| return FT_Err_Ok; |
| } |
| else if ( code == oneMoreByteCode1 ) |
| { |
| if ( FT_READ_BYTE( result_byte ) ) |
| return error; |
| *value = result_byte + lowestUCode; |
| return FT_Err_Ok; |
| } |
| else if ( code == oneMoreByteCode2 ) |
| { |
| if ( FT_READ_BYTE( result_byte ) ) |
| return error; |
| *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 ) |
| { |
| FT_ULong result = 0; |
| FT_Int i; |
| FT_Byte code; |
| FT_Error error = FT_Err_Ok; |
| |
| |
| for ( i = 0; i < 5; ++i ) |
| { |
| code = 0; |
| if ( FT_READ_BYTE( code ) ) |
| return error; |
| |
| /* 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 ); |
| } |
| |
| |
| /* Extend memory of `dst_bytes' buffer and copy data from `src'. */ |
| static FT_Error |
| write_buf( FT_Byte** dst_bytes, |
| FT_ULong* dst_size, |
| FT_ULong* offset, |
| FT_Byte* src, |
| FT_ULong size, |
| FT_Memory memory ) |
| { |
| FT_Error error = FT_Err_Ok; |
| /* We are reallocating memory for `dst', so its pointer may change. */ |
| FT_Byte* dst = *dst_bytes; |
| |
| |
| /* Check whether we are within limits. */ |
| if ( ( *offset + size ) > WOFF2_DEFAULT_MAX_SIZE ) |
| return FT_THROW( Array_Too_Large ); |
| |
| /* Reallocate `dst'. */ |
| if ( ( *offset + size ) > *dst_size ) |
| { |
| FT_TRACE6(( "Reallocating %lu to %lu.\n", |
| *dst_size, (*offset + size) )); |
| if ( FT_QREALLOC( dst, |
| (FT_ULong)( *dst_size ), |
| (FT_ULong)( *offset + size ) ) ) |
| goto Exit; |
| |
| *dst_size = *offset + size; |
| } |
| |
| /* Copy data. */ |
| ft_memcpy( dst + *offset, src, size ); |
| |
| *offset += size; |
| /* Set pointer of `dst' to its correct value. */ |
| *dst_bytes = dst; |
| |
| Exit: |
| return error; |
| } |
| |
| |
| /* Pad buffer to closest multiple of 4. */ |
| static FT_Error |
| pad4( FT_Byte** sfnt_bytes, |
| FT_ULong* sfnt_size, |
| FT_ULong* out_offset, |
| FT_Memory memory ) |
| { |
| FT_Byte* sfnt = *sfnt_bytes; |
| FT_ULong dest_offset = *out_offset; |
| |
| FT_Byte zeroes[] = { 0, 0, 0 }; |
| FT_ULong pad_bytes; |
| |
| |
| if ( dest_offset + 3 < dest_offset ) |
| return FT_THROW( Invalid_Table ); |
| |
| pad_bytes = ROUND4( dest_offset ) - dest_offset; |
| if ( pad_bytes > 0 ) |
| { |
| if ( WRITE_SFNT_BUF( &zeroes[0], pad_bytes ) ) |
| return FT_THROW( Invalid_Table ); |
| } |
| |
| *sfnt_bytes = sfnt; |
| *out_offset = dest_offset; |
| return FT_Err_Ok; |
| } |
| |
| |
| /* Calculate table checksum of `buf'. */ |
| static FT_ULong |
| compute_ULong_sum( FT_Byte* buf, |
| FT_ULong size ) |
| { |
| FT_ULong checksum = 0; |
| FT_ULong aligned_size = size & ~3UL; |
| FT_ULong i; |
| FT_Int shift; |
| |
| |
| for ( i = 0; i < aligned_size; i += 4 ) |
| checksum += FT_NEXT_ULONG( buf ); |
| |
| /* remaining bytes can be shifted and added one at a time */ |
| for ( shift = 24; i < size; i++, shift -= 8 ) |
| checksum += (FT_UInt32)FT_NEXT_BYTE( buf ) << shift; |
| |
| return checksum; |
| } |
| |
| |
| static FT_Error |
| woff2_decompress( FT_Byte* dst, |
| FT_ULong dst_size, |
| const FT_Byte* src, |
| FT_ULong src_size ) |
| { |
| /* this cast is only of importance on 32bit systems; */ |
| /* we don't validate it */ |
| FT_Offset uncompressed_size = (FT_Offset)dst_size; |
| BrotliDecoderResult result; |
| |
| |
| result = BrotliDecoderDecompress( src_size, |
| src, |
| &uncompressed_size, |
| dst ); |
| |
| if ( result != BROTLI_DECODER_RESULT_SUCCESS || |
| uncompressed_size != dst_size ) |
| { |
| FT_ERROR(( "woff2_decompress: Stream length mismatch.\n" )); |
| return FT_THROW( Invalid_Table ); |
| } |
| |
| FT_TRACE2(( "woff2_decompress: Brotli stream decompressed.\n" )); |
| return FT_Err_Ok; |
| } |
| |
| |
| static WOFF2_Table |
| find_table( WOFF2_Table* tables, |
| FT_UShort num_tables, |
| FT_Tag tag ) |
| { |
| FT_Int i; |
| |
| |
| for ( i = 0; i < num_tables; i++ ) |
| { |
| if ( tables[i]->Tag == tag ) |
| return tables[i]; |
| } |
| return NULL; |
| } |
| |
| |
| /* Read `numberOfHMetrics' field from `hhea' table. */ |
| static FT_Error |
| read_num_hmetrics( FT_Stream stream, |
| FT_UShort* num_hmetrics ) |
| { |
| FT_Error error = FT_Err_Ok; |
| FT_UShort num_metrics; |
| |
| |
| if ( FT_STREAM_SKIP( 34 ) ) |
| return FT_THROW( Invalid_Table ); |
| |
| if ( FT_READ_USHORT( num_metrics ) ) |
| return FT_THROW( Invalid_Table ); |
| |
| *num_hmetrics = num_metrics; |
| |
| return error; |
| } |
| |
| |
| /* An auxiliary function for overflow-safe addition. */ |
| static FT_Int |
| with_sign( FT_Byte flag, |
| FT_Int base_val ) |
| { |
| /* Precondition: 0 <= base_val < 65536 (to avoid overflow). */ |
| return ( flag & 1 ) ? base_val : -base_val; |
| } |
| |
| |
| /* An auxiliary function for overflow-safe addition. */ |
| 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; |
| } |
| |
| |
| /* |
| * Decode variable-length (flag, xCoordinate, yCoordinate) triplet for a |
| * simple glyph. See |
| * |
| * https://www.w3.org/TR/WOFF2/#triplet_decoding |
| */ |
| 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_UInt 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; |
| } |
| |
| |
| /* Store decoded points in glyph buffer. */ |
| static FT_Error |
| store_points( FT_ULong n_points, |
| const WOFF2_Point points, |
| FT_UShort n_contours, |
| FT_UShort instruction_len, |
| FT_Bool have_overlap, |
| FT_Byte* dst, |
| FT_ULong dst_size, |
| FT_ULong* glyph_size ) |
| { |
| FT_UInt flag_offset = 10 + ( 2 * n_contours ) + 2 + instruction_len; |
| FT_Byte last_flag = 0xFFU; |
| FT_Byte 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_Byte flag = point.on_curve ? GLYF_ON_CURVE : 0; |
| FT_Int dx = point.x - last_x; |
| FT_Int dy = point.y - last_y; |
| |
| |
| if ( i == 0 && have_overlap ) |
| flag |= GLYF_OVERLAP_SIMPLE; |
| |
| 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_Byte)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_Byte)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_UInt 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; |
| } |
| |
| |
| /* Store loca values (provided by `reconstruct_glyf') to output stream. */ |
| static FT_Error |
| store_loca( FT_ULong* loca_values, |
| FT_ULong loca_values_size, |
| FT_UShort index_format, |
| FT_ULong* checksum, |
| FT_Byte** sfnt_bytes, |
| FT_ULong* sfnt_size, |
| FT_ULong* out_offset, |
| FT_Memory memory ) |
| { |
| FT_Error error = FT_Err_Ok; |
| FT_Byte* sfnt = *sfnt_bytes; |
| FT_ULong dest_offset = *out_offset; |
| |
| FT_Byte* loca_buf = NULL; |
| FT_Byte* dst = NULL; |
| |
| FT_UInt i = 0; |
| FT_ULong loca_buf_size; |
| |
| const FT_ULong offset_size = index_format ? 4 : 2; |
| |
| |
| if ( ( loca_values_size << 2 ) >> 2 != loca_values_size ) |
| goto Fail; |
| |
| loca_buf_size = loca_values_size * offset_size; |
| if ( FT_QALLOC( loca_buf, loca_buf_size ) ) |
| goto Fail; |
| |
| dst = loca_buf; |
| for ( i = 0; i < loca_values_size; i++ ) |
| { |
| FT_ULong value = loca_values[i]; |
| |
| |
| if ( index_format ) |
| WRITE_ULONG( dst, value ); |
| else |
| WRITE_USHORT( dst, ( value >> 1 ) ); |
| } |
| |
| *checksum = compute_ULong_sum( loca_buf, loca_buf_size ); |
| /* Write `loca' table to sfnt buffer. */ |
| if ( WRITE_SFNT_BUF( loca_buf, loca_buf_size ) ) |
| goto Fail; |
| |
| /* Set pointer `sfnt_bytes' to its correct value. */ |
| *sfnt_bytes = sfnt; |
| *out_offset = dest_offset; |
| |
| FT_FREE( loca_buf ); |
| return error; |
| |
| Fail: |
| if ( !error ) |
| error = FT_THROW( Invalid_Table ); |
| |
| FT_FREE( loca_buf ); |
| |
| return error; |
| } |
| |
| |
| static FT_Error |
| reconstruct_glyf( FT_Stream stream, |
| FT_ULong* glyf_checksum, |
| FT_ULong* loca_checksum, |
| FT_Byte** sfnt_bytes, |
| FT_ULong* sfnt_size, |
| 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 option_flags; |
| FT_UShort num_glyphs; |
| FT_UShort index_format; |
| FT_ULong expected_loca_length; |
| FT_UInt offset; |
| FT_UInt i; |
| FT_ULong points_size; |
| FT_ULong glyph_buf_size; |
| FT_ULong bbox_bitmap_offset; |
| FT_ULong bbox_bitmap_length; |
| FT_ULong overlap_bitmap_offset = 0; |
| FT_ULong overlap_bitmap_length = 0; |
| |
| 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_QNEW_ARRAY( substreams, num_substreams ) ) |
| goto Fail; |
| |
| if ( FT_STREAM_SKIP( 2 ) ) |
| goto Fail; |
| if ( FT_READ_USHORT( option_flags ) ) |
| goto Fail; |
| if ( FT_READ_USHORT( num_glyphs ) ) |
| goto Fail; |
| if ( FT_READ_USHORT( index_format ) ) |
| goto Fail; |
| |
| FT_TRACE4(( "option_flags = %u; num_glyphs = %u; index_format = %u\n", |
| option_flags, num_glyphs, index_format )); |
| |
| info->num_glyphs = num_glyphs; |
| |
| /* 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 ( info->loca_table->dst_length != expected_loca_length ) |
| goto Fail; |
| |
| offset = 2 + 2 + 2 + 2 + ( num_substreams * 4 ); |
| if ( offset > info->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 > info->glyf_table->TransformLength - offset ) |
| goto Fail; |
| |
| substreams[i].start = pos + offset; |
| substreams[i].offset = pos + offset; |
| substreams[i].size = substream_size; |
| |
| FT_TRACE5(( " Substream %d: offset = %lu; size = %lu;\n", |
| i, substreams[i].offset, substreams[i].size )); |
| offset += substream_size; |
| } |
| |
| if ( option_flags & HAVE_OVERLAP_SIMPLE_BITMAP ) |
| { |
| /* Size of overlapBitmap = floor((numGlyphs + 7) / 8) */ |
| overlap_bitmap_length = ( num_glyphs + 7U ) >> 3; |
| if ( overlap_bitmap_length > info->glyf_table->TransformLength - offset ) |
| goto Fail; |
| |
| overlap_bitmap_offset = pos + offset; |
| |
| FT_TRACE5(( " Overlap bitmap: offset = %lu; size = %lu;\n", |
| overlap_bitmap_offset, overlap_bitmap_length )); |
| offset += overlap_bitmap_length; |
| } |
| |
| if ( FT_QNEW_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) */ |
| bbox_bitmap_length = ( ( num_glyphs + 31U ) >> 5 ) << 2; |
| /* bboxStreamSize is the combined size of bboxBitmap and bboxStream. */ |
| substreams[BBOX_STREAM].offset += bbox_bitmap_length; |
| |
| glyph_buf_size = WOFF2_DEFAULT_GLYPH_BUF; |
| if ( FT_QALLOC( glyph_buf, glyph_buf_size ) ) |
| goto Fail; |
| |
| if ( FT_QNEW_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 = 0; |
| |
| |
| /* 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 = 0; |
| 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_QREALLOC( 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_Bool have_overlap = FALSE; |
| FT_Byte overlap_bitmap; |
| FT_ULong overlap_offset; |
| FT_Byte* flags_buf = NULL; |
| FT_Byte* triplet_buf = NULL; |
| FT_UShort instruction_size; |
| FT_ULong size_needed; |
| FT_Int end_point; |
| FT_UInt contour_ix; |
| |
| FT_Byte* pointer = NULL; |
| |
| |
| /* Set `have_overlap`. */ |
| if ( overlap_bitmap_offset ) |
| { |
| overlap_offset = overlap_bitmap_offset + ( i >> 3 ); |
| if ( FT_STREAM_SEEK( overlap_offset ) || |
| FT_READ_BYTE( overlap_bitmap ) ) |
| goto Fail; |
| if ( overlap_bitmap & ( 0x80 >> ( i & 7 ) ) ) |
| have_overlap = TRUE; |
| } |
| |
| if ( FT_QNEW_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; |
| |
| if ( substreams[GLYPH_STREAM].size < |
| ( substreams[GLYPH_STREAM].offset - |
| substreams[GLYPH_STREAM].start ) ) |
| goto Fail; |
| |
| 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_QNEW_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 ) ) |
| goto Fail; |
| |
| size_needed = 12 + |
| ( 2 * n_contours ) + |
| ( 5 * total_n_points ) + |
| instruction_size; |
| if ( glyph_buf_size < size_needed ) |
| { |
| if ( FT_QREALLOC( 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; |
| |
| substreams[INSTRUCTION_STREAM].offset += instruction_size; |
| glyph_size += instruction_size; |
| |
| if ( store_points( total_n_points, |
| points, |
| n_contours, |
| instruction_size, |
| have_overlap, |
| 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; |
| |
| if ( pad4( &sfnt, sfnt_size, &dest_offset, memory ) ) |
| goto Fail; |
| |
| *glyf_checksum += compute_ULong_sum( glyph_buf, glyph_size ); |
| |
| /* Store x_mins, may be required to reconstruct `hmtx'. */ |
| info->x_mins[i] = (FT_Short)x_min; |
| } |
| |
| info->glyf_table->dst_length = dest_offset - info->glyf_table->dst_offset; |
| info->loca_table->dst_offset = dest_offset; |
| |
| /* `loca[n]' will be equal to the length of the `glyf' table. */ |
| loca_values[num_glyphs] = info->glyf_table->dst_length; |
| |
| if ( store_loca( loca_values, |
| num_glyphs + 1, |
| index_format, |
| loca_checksum, |
| &sfnt, |
| sfnt_size, |
| &dest_offset, |
| memory ) ) |
| goto Fail; |
| |
| info->loca_table->dst_length = dest_offset - info->loca_table->dst_offset; |
| |
| FT_TRACE4(( " loca table info:\n" )); |
| FT_TRACE4(( " dst_offset = %lu\n", info->loca_table->dst_offset )); |
| FT_TRACE4(( " dst_length = %lu\n", info->loca_table->dst_length )); |
| FT_TRACE4(( " checksum = %09lx\n", *loca_checksum )); |
| |
| /* Set pointer `sfnt_bytes' to its correct value. */ |
| *sfnt_bytes = sfnt; |
| *out_offset = dest_offset; |
| |
| 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 ); |
| |
| /* Set pointer `sfnt_bytes' to its correct value. */ |
| *sfnt_bytes = sfnt; |
| |
| FT_FREE( substreams ); |
| FT_FREE( loca_values ); |
| FT_FREE( n_points_arr ); |
| FT_FREE( glyph_buf ); |
| FT_FREE( points ); |
| |
| return error; |
| } |
| |
| |
| /* Get `x_mins' for untransformed `glyf' table. */ |
| static FT_Error |
| get_x_mins( FT_Stream stream, |
| WOFF2_Table* tables, |
| FT_UShort num_tables, |
| WOFF2_Info info, |
| FT_Memory memory ) |
| { |
| FT_UShort num_glyphs; |
| FT_UShort index_format; |
| FT_ULong glyf_offset; |
| FT_UShort glyf_offset_short; |
| FT_ULong loca_offset; |
| FT_Int i; |
| FT_Error error = FT_Err_Ok; |
| FT_ULong offset_size; |
| |
| /* At this point of time those tables might not have been read yet. */ |
| const WOFF2_Table maxp_table = find_table( tables, num_tables, |
| TTAG_maxp ); |
| const WOFF2_Table head_table = find_table( tables, num_tables, |
| TTAG_head ); |
| |
| |
| if ( !maxp_table ) |
| { |
| FT_ERROR(( "`maxp' table is missing.\n" )); |
| return FT_THROW( Invalid_Table ); |
| } |
| |
| if ( !head_table ) |
| { |
| FT_ERROR(( "`head' table is missing.\n" )); |
| return FT_THROW( Invalid_Table ); |
| } |
| |
| if ( !info->loca_table ) |
| { |
| FT_ERROR(( "`loca' table is missing.\n" )); |
| return FT_THROW( Invalid_Table ); |
| } |
| |
| /* Read `numGlyphs' field from `maxp' table. */ |
| if ( FT_STREAM_SEEK( maxp_table->src_offset ) || FT_STREAM_SKIP( 8 ) ) |
| return error; |
| |
| if ( FT_READ_USHORT( num_glyphs ) ) |
| return error; |
| |
| info->num_glyphs = num_glyphs; |
| |
| /* Read `indexToLocFormat' field from `head' table. */ |
| if ( FT_STREAM_SEEK( head_table->src_offset ) || |
| FT_STREAM_SKIP( 50 ) ) |
| return error; |
| |
| if ( FT_READ_USHORT( index_format ) ) |
| return error; |
| |
| offset_size = index_format ? 4 : 2; |
| |
| /* Create `x_mins' array. */ |
| if ( FT_QNEW_ARRAY( info->x_mins, num_glyphs ) ) |
| return error; |
| |
| loca_offset = info->loca_table->src_offset; |
| |
| for ( i = 0; i < num_glyphs; ++i ) |
| { |
| if ( FT_STREAM_SEEK( loca_offset ) ) |
| return error; |
| |
| loca_offset += offset_size; |
| |
| if ( index_format ) |
| { |
| if ( FT_READ_ULONG( glyf_offset ) ) |
| return error; |
| } |
| else |
| { |
| if ( FT_READ_USHORT( glyf_offset_short ) ) |
| return error; |
| |
| glyf_offset = (FT_ULong)( glyf_offset_short ); |
| glyf_offset = glyf_offset << 1; |
| } |
| |
| glyf_offset += info->glyf_table->src_offset; |
| |
| if ( FT_STREAM_SEEK( glyf_offset ) || FT_STREAM_SKIP( 2 ) ) |
| return error; |
| |
| if ( FT_READ_SHORT( info->x_mins[i] ) ) |
| return error; |
| } |
| |
| return error; |
| } |
| |
| |
| static FT_Error |
| reconstruct_hmtx( FT_Stream stream, |
| FT_UShort num_glyphs, |
| FT_UShort num_hmetrics, |
| FT_Short* x_mins, |
| FT_ULong* checksum, |
| FT_Byte** sfnt_bytes, |
| FT_ULong* sfnt_size, |
| FT_ULong* out_offset, |
| FT_Memory memory ) |
| { |
| FT_Error error = FT_Err_Ok; |
| FT_Byte* sfnt = *sfnt_bytes; |
| FT_ULong dest_offset = *out_offset; |
| |
| FT_Byte hmtx_flags; |
| FT_Bool has_proportional_lsbs, has_monospace_lsbs; |
| FT_ULong hmtx_table_size; |
| FT_Int i; |
| |
| FT_UShort* advance_widths = NULL; |
| FT_Short* lsbs = NULL; |
| FT_Byte* hmtx_table = NULL; |
| FT_Byte* dst = NULL; |
| |
| |
| if ( FT_READ_BYTE( hmtx_flags ) ) |
| goto Fail; |
| |
| has_proportional_lsbs = ( hmtx_flags & 1 ) == 0; |
| has_monospace_lsbs = ( hmtx_flags & 2 ) == 0; |
| |
| /* Bits 2-7 are reserved and MUST be zero. */ |
| if ( ( hmtx_flags & 0xFC ) != 0 ) |
| goto Fail; |
| |
| /* Are you REALLY transformed? */ |
| if ( has_proportional_lsbs && has_monospace_lsbs ) |
| goto Fail; |
| |
| /* Cannot have a transformed `hmtx' without `glyf'. */ |
| if ( ( num_hmetrics > num_glyphs ) || |
| ( num_hmetrics < 1 ) ) |
| goto Fail; |
| |
| /* Must have at least one entry. */ |
| if ( num_hmetrics < 1 ) |
| goto Fail; |
| |
| if ( FT_QNEW_ARRAY( advance_widths, num_hmetrics ) || |
| FT_QNEW_ARRAY( lsbs, num_glyphs ) ) |
| goto Fail; |
| |
| /* Read `advanceWidth' stream. Always present. */ |
| for ( i = 0; i < num_hmetrics; i++ ) |
| { |
| FT_UShort advance_width; |
| |
| |
| if ( FT_READ_USHORT( advance_width ) ) |
| goto Fail; |
| |
| advance_widths[i] = advance_width; |
| } |
| |
| /* lsb values for proportional glyphs. */ |
| for ( i = 0; i < num_hmetrics; i++ ) |
| { |
| FT_Short lsb; |
| |
| |
| if ( has_proportional_lsbs ) |
| { |
| if ( FT_READ_SHORT( lsb ) ) |
| goto Fail; |
| } |
| else |
| lsb = x_mins[i]; |
| |
| lsbs[i] = lsb; |
| } |
| |
| /* lsb values for monospaced glyphs. */ |
| for ( i = num_hmetrics; i < num_glyphs; i++ ) |
| { |
| FT_Short lsb; |
| |
| |
| if ( has_monospace_lsbs ) |
| { |
| if ( FT_READ_SHORT( lsb ) ) |
| goto Fail; |
| } |
| else |
| lsb = x_mins[i]; |
| |
| lsbs[i] = lsb; |
| } |
| |
| /* Build the hmtx table. */ |
| hmtx_table_size = 2 * num_hmetrics + 2 * num_glyphs; |
| if ( FT_QALLOC( hmtx_table, hmtx_table_size ) ) |
| goto Fail; |
| |
| dst = hmtx_table; |
| FT_TRACE6(( "hmtx values: \n" )); |
| for ( i = 0; i < num_glyphs; i++ ) |
| { |
| if ( i < num_hmetrics ) |
| { |
| WRITE_SHORT( dst, advance_widths[i] ); |
| FT_TRACE6(( "%d ", advance_widths[i] )); |
| } |
| |
| WRITE_SHORT( dst, lsbs[i] ); |
| FT_TRACE6(( "%d ", lsbs[i] )); |
| } |
| FT_TRACE6(( "\n" )); |
| |
| *checksum = compute_ULong_sum( hmtx_table, hmtx_table_size ); |
| /* Write `hmtx' table to sfnt buffer. */ |
| if ( WRITE_SFNT_BUF( hmtx_table, hmtx_table_size ) ) |
| goto Fail; |
| |
| /* Set pointer `sfnt_bytes' to its correct value. */ |
| *sfnt_bytes = sfnt; |
| *out_offset = dest_offset; |
| |
| FT_FREE( advance_widths ); |
| FT_FREE( lsbs ); |
| FT_FREE( hmtx_table ); |
| |
| return error; |
| |
| Fail: |
| FT_FREE( advance_widths ); |
| FT_FREE( lsbs ); |
| FT_FREE( hmtx_table ); |
| |
| if ( !error ) |
| error = FT_THROW( Invalid_Table ); |
| |
| 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_ULong* sfnt_size, |
| FT_Memory memory ) |
| { |
| /* Memory management of `transformed_buf' is handled by the caller. */ |
| |
| FT_Error error = FT_Err_Ok; |
| FT_Stream stream = NULL; |
| FT_Byte* buf_cursor = NULL; |
| FT_Byte table_entry[16]; |
| |
| /* We are reallocating memory for `sfnt', so its pointer may change. */ |
| FT_Byte* sfnt = *sfnt_bytes; |
| |
| FT_UShort num_tables = woff2->num_tables; |
| FT_ULong dest_offset = 12 + num_tables * 16UL; |
| |
| FT_ULong checksum = 0; |
| FT_ULong loca_checksum = 0; |
| FT_Int nn = 0; |
| FT_UShort num_hmetrics = 0; |
| FT_ULong font_checksum = info->header_checksum; |
| FT_Bool is_glyf_xform = FALSE; |
| |
| FT_ULong table_entry_offset = 12; |
| |
| |
| /* A few table checks before reconstruction. */ |
| /* `glyf' must be present with `loca'. */ |
| info->glyf_table = find_table( indices, num_tables, TTAG_glyf ); |
| info->loca_table = find_table( indices, num_tables, TTAG_loca ); |
| |
| if ( ( info->glyf_table == NULL ) ^ ( info->loca_table == NULL ) ) |
| { |
| FT_ERROR(( "One of `glyf'/`loca' tables missing.\n" )); |
| return FT_THROW( Invalid_Table ); |
| } |
| |
| /* Both `glyf' and `loca' must have same transformation. */ |
| if ( info->glyf_table != NULL ) |
| { |
| if ( ( info->glyf_table->flags & WOFF2_FLAGS_TRANSFORM ) != |
| ( info->loca_table->flags & WOFF2_FLAGS_TRANSFORM ) ) |
| { |
| FT_ERROR(( "Transformation mismatch" |
| " between `glyf' and `loca' table." )); |
| return FT_THROW( Invalid_Table ); |
| } |
| } |
| |
| /* Create a stream for the uncompressed buffer. */ |
| if ( FT_NEW( stream ) ) |
| goto Fail; |
| FT_Stream_OpenMemory( stream, transformed_buf, transformed_buf_size ); |
| |
| FT_ASSERT( FT_STREAM_POS() == 0 ); |
| |
| /* Reconstruct/copy tables to output stream. */ |
| for ( nn = 0; nn < num_tables; nn++ ) |
| { |
| WOFF2_TableRec table = *( indices[nn] ); |
| |
| |
| FT_TRACE3(( "Seeking to %ld with table size %ld.\n", |
| table.src_offset, table.src_length )); |
| FT_TRACE3(( "Table tag: %c%c%c%c.\n", |
| (FT_Char)( table.Tag >> 24 ), |
| (FT_Char)( table.Tag >> 16 ), |
| (FT_Char)( table.Tag >> 8 ), |
| (FT_Char)( table.Tag ) )); |
| |
| if ( FT_STREAM_SEEK( table.src_offset ) ) |
| goto Fail; |
| |
| if ( table.src_offset + table.src_length > transformed_buf_size ) |
| goto Fail; |
| |
| /* Get stream size for fields of `hmtx' table. */ |
| if ( table.Tag == TTAG_hhea ) |
| { |
| if ( read_num_hmetrics( stream, &num_hmetrics ) ) |
| goto Fail; |
| } |
| |
| info->num_hmetrics = num_hmetrics; |
| |
| checksum = 0; |
| if ( ( table.flags & WOFF2_FLAGS_TRANSFORM ) != WOFF2_FLAGS_TRANSFORM ) |
| { |
| /* Check whether `head' is at least 12 bytes. */ |
| if ( table.Tag == TTAG_head ) |
| { |
| if ( table.src_length < 12 ) |
| goto Fail; |
| |
| buf_cursor = transformed_buf + table.src_offset + 8; |
| /* Set checkSumAdjustment = 0 */ |
| WRITE_ULONG( buf_cursor, 0 ); |
| } |
| |
| table.dst_offset = dest_offset; |
| |
| checksum = compute_ULong_sum( transformed_buf + table.src_offset, |
| table.src_length ); |
| FT_TRACE4(( "Checksum = %09lx.\n", checksum )); |
| |
| if ( WRITE_SFNT_BUF( transformed_buf + table.src_offset, |
| table.src_length ) ) |
| goto Fail; |
| } |
| else |
| { |
| FT_TRACE3(( "This table is transformed.\n" )); |
| |
| if ( table.Tag == TTAG_glyf ) |
| { |
| is_glyf_xform = TRUE; |
| table.dst_offset = dest_offset; |
| |
| if ( reconstruct_glyf( stream, |
| &checksum, |
| &loca_checksum, |
| &sfnt, |
| sfnt_size, |
| &dest_offset, |
| info, |
| memory ) ) |
| goto Fail; |
| |
| FT_TRACE4(( "Checksum = %09lx.\n", checksum )); |
| } |
| |
| else if ( table.Tag == TTAG_loca ) |
| checksum = loca_checksum; |
| |
| else if ( table.Tag == TTAG_hmtx ) |
| { |
| /* If glyf is not transformed and hmtx is, handle separately. */ |
| if ( !is_glyf_xform ) |
| { |
| if ( get_x_mins( stream, indices, num_tables, info, memory ) ) |
| goto Fail; |
| } |
| |
| table.dst_offset = dest_offset; |
| |
| if ( reconstruct_hmtx( stream, |
| info->num_glyphs, |
| info->num_hmetrics, |
| info->x_mins, |
| &checksum, |
| &sfnt, |
| sfnt_size, |
| &dest_offset, |
| memory ) ) |
| goto Fail; |
| } |
| else |
| { |
| /* Unknown transform. */ |
| FT_ERROR(( "Unknown table transform.\n" )); |
| goto Fail; |
| } |
| } |
| |
| font_checksum += checksum; |
| |
| buf_cursor = &table_entry[0]; |
| WRITE_ULONG( buf_cursor, table.Tag ); |
| WRITE_ULONG( buf_cursor, checksum ); |
| WRITE_ULONG( buf_cursor, table.dst_offset ); |
| WRITE_ULONG( buf_cursor, table.dst_length ); |
| |
| WRITE_SFNT_BUF_AT( table_entry_offset, table_entry, 16 ); |
| |
| /* Update checksum. */ |
| font_checksum += compute_ULong_sum( table_entry, 16 ); |
| |
| if ( pad4( &sfnt, sfnt_size, &dest_offset, memory ) ) |
| goto Fail; |
| |
| /* Sanity check. */ |
| if ( (FT_ULong)( table.dst_offset + table.dst_length ) > dest_offset ) |
| { |
| FT_ERROR(( "Table was partially written.\n" )); |
| goto Fail; |
| } |
| } |
| |
| /* Update `head' checkSumAdjustment. */ |
| info->head_table = find_table( indices, num_tables, TTAG_head ); |
| if ( !info->head_table ) |
| { |
| FT_ERROR(( "`head' table is missing.\n" )); |
| goto Fail; |
| } |
| |
| if ( info->head_table->dst_length < 12 ) |
| goto Fail; |
| |
| buf_cursor = sfnt + info->head_table->dst_offset + 8; |
| font_checksum = 0xB1B0AFBA - font_checksum; |
| |
| WRITE_ULONG( buf_cursor, font_checksum ); |
| |
| FT_TRACE2(( "Final checksum = %09lx.\n", font_checksum )); |
| |
| woff2->actual_sfnt_size = dest_offset; |
| |
| /* Set pointer of sfnt stream to its correct value. */ |
| *sfnt_bytes = sfnt; |
| |
| FT_Stream_Close( stream ); |
| FT_FREE( stream ); |
| |
| return error; |
| |
| Fail: |
| if ( !error ) |
| error = FT_THROW( Invalid_Table ); |
| |
| /* Set pointer of sfnt stream to its correct value. */ |
| *sfnt_bytes = sfnt; |
| |
| FT_Stream_Close( stream ); |
| FT_FREE( stream ); |
| |
| return error; |
| } |
| |
| |
| /* 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, |
| TT_Face face, |
| FT_Int* face_instance_index, |
| FT_Long* num_faces ) |
| { |
| FT_Memory memory = stream->memory; |
| FT_Error error = FT_Err_Ok; |
| FT_Int face_index; |
| |
| WOFF2_HeaderRec woff2; |
| WOFF2_InfoRec info = { 0, 0, 0, NULL, NULL, NULL, NULL }; |
| WOFF2_Table tables = NULL; |
| WOFF2_Table* indices = NULL; |
| WOFF2_Table* temp_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_UInt glyf_index; |
| FT_UInt loca_index; |
| FT_UInt32 file_offset; |
| |
| FT_Byte* sfnt = NULL; |
| FT_Stream sfnt_stream = NULL; |
| FT_ULong sfnt_size; |
| |
| FT_Byte* uncompressed_buf = NULL; |
| |
| 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 ), |
| FT_FRAME_ULONG ( totalSfntSize ), |
| 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 |
| }; |
| |
| |
| FT_ASSERT( stream == face->root.stream ); |
| FT_ASSERT( FT_STREAM_POS() == 0 ); |
| |
| face_index = FT_ABS( *face_instance_index ) & 0xFFFF; |
| |
| /* Read WOFF2 Header. */ |
| if ( FT_STREAM_READ_FIELDS( woff2_header_fields, &woff2 ) ) |
| return error; |
| |
| FT_TRACE4(( "signature -> 0x%lX\n", woff2.signature )); |
| FT_TRACE2(( "flavor -> 0x%08lx\n", woff2.flavor )); |
| FT_TRACE4(( "length -> %lu\n", woff2.length )); |
| FT_TRACE2(( "num_tables -> %hu\n", woff2.num_tables )); |
| FT_TRACE4(( "totalSfntSize -> %lu\n", woff2.totalSfntSize )); |
| FT_TRACE4(( "metaOffset -> %lu\n", woff2.metaOffset )); |
| FT_TRACE4(( "metaLength -> %lu\n", woff2.metaLength )); |
| FT_TRACE4(( "privOffset -> %lu\n", woff2.privOffset )); |
| FT_TRACE4(( "privLength -> %lu\n", woff2.privLength )); |
| |
| /* 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 || |
| woff2.num_tables > 0xFFFU || |
| 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 ) ) |
| { |
| FT_ERROR(( "woff2_open_font: invalid WOFF2 header\n" )); |
| return FT_THROW( Invalid_Table ); |
| } |
| |
| FT_TRACE2(( "woff2_open_font: WOFF2 Header is valid.\n" )); |
| |
| woff2.ttc_fonts = NULL; |
| |
| /* Read table directory. */ |
| if ( FT_QNEW_ARRAY( tables, woff2.num_tables ) || |
| FT_QNEW_ARRAY( indices, woff2.num_tables ) ) |
| goto Exit; |
| |
| FT_TRACE2(( "\n" )); |
| FT_TRACE2(( " tag flags transform origLen transformLen offset\n" )); |
| FT_TRACE2(( " -----------------------------------------------------------\n" )); |
| /* " XXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX" */ |
| |
| 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 = woff2_known_tags( table->FlagByte & 0x3f ); |
| if ( !table->Tag ) |
| { |
| FT_ERROR(( "woff2_open_font: Unknown table tag." )); |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| } |
| |
| 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->dst_length ) ) |
| goto Exit; |
| |
| table->TransformLength = table->dst_length; |
| |
| if ( ( flags & WOFF2_FLAGS_TRANSFORM ) != 0 ) |
| { |
| if ( READ_BASE128( table->TransformLength ) ) |
| goto Exit; |
| |
| if ( table->Tag == TTAG_loca && table->TransformLength ) |
| { |
| FT_ERROR(( "woff2_open_font: Invalid loca `transformLength'.\n" )); |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| } |
| |
| if ( src_offset + table->TransformLength < src_offset ) |
| { |
| FT_ERROR(( "woff2_open_font: invalid WOFF2 table directory.\n" )); |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| table->flags = flags; |
| table->src_offset = src_offset; |
| table->src_length = table->TransformLength; |
| src_offset += table->TransformLength; |
| table->dst_offset = 0; |
| |
| FT_TRACE2(( " %c%c%c%c %08d %08d %08ld %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->dst_length, |
| table->TransformLength, |
| 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 ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| FT_TRACE2(( "Table directory parsed.\n" )); |
| |
| /* Check for and read collection directory. */ |
| woff2.num_fonts = 1; |
| 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; |
| |
| if ( woff2.header_version != 0x00010000 && |
| woff2.header_version != 0x00020000 ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| if ( READ_255USHORT( woff2.num_fonts ) ) |
| goto Exit; |
| |
| if ( !woff2.num_fonts ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| FT_TRACE4(( "Number of fonts in TTC: %d\n", woff2.num_fonts )); |
| |
| /* pre-zero pointers within in case of failure */ |
| 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_font->num_tables ) ) |
| goto Exit; |
| if ( FT_READ_ULONG( ttc_font->flavor ) ) |
| goto Exit; |
| |
| if ( FT_QNEW_ARRAY( ttc_font->table_indices, ttc_font->num_tables ) ) |
| goto Exit; |
| |
| FT_TRACE5(( "Number of tables in font %d: %d\n", |
| nn, ttc_font->num_tables )); |
| |
| #ifdef FT_DEBUG_LEVEL_TRACE |
| if ( ttc_font->num_tables ) |
| FT_TRACE6(( " Indices: " )); |
| #endif |
| |
| glyf_index = 0; |
| loca_index = 0; |
| |
| for ( j = 0; j < ttc_font->num_tables; j++ ) |
| { |
| FT_UShort table_index; |
| WOFF2_Table table; |
| |
| |
| if ( READ_255USHORT( table_index ) ) |
| goto Exit; |
| |
| FT_TRACE6(( "%hu ", table_index )); |
| if ( table_index >= woff2.num_tables ) |
| { |
| FT_ERROR(( "woff2_open_font: invalid table index\n" )); |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| 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; |
| } |
| |
| #ifdef FT_DEBUG_LEVEL_TRACE |
| if ( ttc_font->num_tables ) |
| FT_TRACE6(( "\n" )); |
| #endif |
| |
| /* glyf and loca must be consecutive */ |
| if ( glyf_index > 0 || loca_index > 0 ) |
| { |
| if ( glyf_index > loca_index || |
| loca_index - glyf_index != 1 ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| } |
| } |
| |
| /* Collection directory reading complete. */ |
| FT_TRACE2(( "WOFF2 collection directory is valid.\n" )); |
| } |
| else |
| woff2.ttc_fonts = NULL; |
| |
| woff2.compressed_offset = FT_STREAM_POS(); |
| file_offset = ROUND4( woff2.compressed_offset + |
| woff2.totalCompressedSize ); |
| |
| /* Some more checks before we start reading the tables. */ |
| if ( file_offset > woff2.length ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| if ( woff2.metaOffset ) |
| { |
| if ( file_offset != woff2.metaOffset ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| file_offset = ROUND4( woff2.metaOffset + woff2.metaLength ); |
| } |
| |
| if ( woff2.privOffset ) |
| { |
| if ( file_offset != woff2.privOffset ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| file_offset = ROUND4( woff2.privOffset + woff2.privLength ); |
| } |
| |
| if ( file_offset != ( ROUND4( woff2.length ) ) ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| /* Validate requested face index. */ |
| *num_faces = woff2.num_fonts; |
| /* value -(N+1) requests information on index N */ |
| if ( *face_instance_index < 0 && face_index > 0 ) |
| face_index--; |
| |
| if ( face_index >= woff2.num_fonts ) |
| { |
| if ( *face_instance_index >= 0 ) |
| { |
| error = FT_THROW( Invalid_Argument ); |
| goto Exit; |
| } |
| else |
| face_index = 0; |
| } |
| |
| /* Only retain tables of the requested face in a TTC. */ |
| if ( woff2.header_version ) |
| { |
| WOFF2_TtcFont ttc_font = woff2.ttc_fonts + face_index; |
| |
| |
| if ( ttc_font->num_tables == 0 || ttc_font->num_tables > 0xFFFU ) |
| { |
| FT_ERROR(( "woff2_open_font: invalid WOFF2 CollectionFontEntry\n" )); |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| /* Create a temporary array. */ |
| if ( FT_QNEW_ARRAY( temp_indices, |
| ttc_font->num_tables ) ) |
| goto Exit; |
| |
| FT_TRACE4(( "Storing tables for TTC face index %d.\n", face_index )); |
| for ( nn = 0; nn < ttc_font->num_tables; nn++ ) |
| temp_indices[nn] = indices[ttc_font->table_indices[nn]]; |
| |
| /* Resize array to required size. */ |
| if ( FT_QRENEW_ARRAY( indices, |
| woff2.num_tables, |
| ttc_font->num_tables ) ) |
| goto Exit; |
| |
| for ( nn = 0; nn < ttc_font->num_tables; nn++ ) |
| indices[nn] = temp_indices[nn]; |
| |
| FT_FREE( temp_indices ); |
| |
| /* Change header values. */ |
| woff2.flavor = ttc_font->flavor; |
| woff2.num_tables = ttc_font->num_tables; |
| } |
| |
| /* We need to allocate this much at the minimum. */ |
| sfnt_size = 12 + woff2.num_tables * 16UL; |
| /* This is what we normally expect. */ |
| /* Initially trust `totalSfntSize' and change later as required. */ |
| if ( woff2.totalSfntSize > sfnt_size ) |
| { |
| /* However, adjust the value to something reasonable. */ |
| |
| /* Factor 64 is heuristic. */ |
| if ( ( woff2.totalSfntSize >> 6 ) > woff2.length ) |
| sfnt_size = woff2.length << 6; |
| else |
| sfnt_size = woff2.totalSfntSize; |
| |
| if ( sfnt_size >= MAX_SFNT_SIZE ) |
| sfnt_size = MAX_SFNT_SIZE; |
| |
| #ifdef FT_DEBUG_LEVEL_TRACE |
| if ( sfnt_size != woff2.totalSfntSize ) |
| FT_TRACE4(( "adjusting estimate of uncompressed font size" |
| " to %lu bytes\n", |
| sfnt_size )); |
| #endif |
| } |
| |
| /* Write sfnt header. */ |
| if ( FT_QALLOC( sfnt, sfnt_size ) || |
| FT_NEW( sfnt_stream ) ) |
| goto Exit; |
| |
| { |
| FT_Byte* sfnt_header = sfnt; |
| |
| FT_Int entrySelector = FT_MSB( woff2.num_tables ); |
| FT_Int searchRange = ( 1 << entrySelector ) * 16; |
| FT_Int 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 ); |
| } |
| |
| info.header_checksum = compute_ULong_sum( sfnt, 12 ); |
| |
| /* Sort tables by tag. */ |
| ft_qsort( indices, |
| woff2.num_tables, |
| sizeof ( WOFF2_Table ), |
| compare_tags ); |
| |
| /* reject fonts that have multiple tables with the same tag */ |
| for ( nn = 1; nn < woff2.num_tables; nn++ ) |
| { |
| FT_Tag tag = indices[nn]->Tag; |
| |
| |
| if ( tag == indices[nn - 1]->Tag ) |
| { |
| FT_ERROR(( "woff2_open_font:" |
| " multiple tables with tag `%c%c%c%c'.\n", |
| (FT_Char)( tag >> 24 ), |
| (FT_Char)( tag >> 16 ), |
| (FT_Char)( tag >> 8 ), |
| (FT_Char)( tag ) )); |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| } |
| |
| if ( woff2.uncompressed_size < 1 ) |
| { |
| error = FT_THROW( Invalid_Table ); |
| goto Exit; |
| } |
| |
| /* We must not blindly trust `uncompressed_size` since its */ |
| /* value might be corrupted. If it is too large, reject the */ |
| /* font. In other words, we don't accept a WOFF2 font that */ |
| /* expands to something larger than MAX_SFNT_SIZE. If ever */ |
| /* necessary, this limit can be easily adjusted. */ |
| if ( woff2.uncompressed_size > MAX_SFNT_SIZE ) |
| { |
| FT_ERROR(( "Uncompressed font too large.\n" )); |
| error = FT_THROW( Array_Too_Large ); |
| goto Exit; |
| } |
| |
| /* Allocate memory for uncompressed table data. */ |
| if ( FT_QALLOC( uncompressed_buf, woff2.uncompressed_size ) || |
| FT_FRAME_ENTER( woff2.totalCompressedSize ) ) |
| goto Exit; |
| |
| /* Uncompress the stream. */ |
| error = woff2_decompress( uncompressed_buf, |
| woff2.uncompressed_size, |
| stream->cursor, |
| woff2.totalCompressedSize ); |
| |
| FT_FRAME_EXIT(); |
| |
| if ( error ) |
| goto Exit; |
| |
| error = reconstruct_font( uncompressed_buf, |
| woff2.uncompressed_size, |
| indices, |
| &woff2, |
| &info, |
| &sfnt, |
| &sfnt_size, |
| memory ); |
| |
| if ( error ) |
| goto Exit; |
| |
| /* Resize `sfnt' to actual size of sfnt stream. */ |
| if ( woff2.actual_sfnt_size < sfnt_size ) |
| { |
| FT_TRACE5(( "Trimming sfnt stream from %lu to %lu.\n", |
| sfnt_size, woff2.actual_sfnt_size )); |
| if ( FT_QREALLOC( sfnt, |
| (FT_ULong)( sfnt_size ), |
| (FT_ULong)( woff2.actual_sfnt_size ) ) ) |
| goto Exit; |
| } |
| |
| /* `reconstruct_font' has done all the work. */ |
| /* Swap out stream and return. */ |
| FT_Stream_OpenMemory( sfnt_stream, sfnt, woff2.actual_sfnt_size ); |
| sfnt_stream->memory = stream->memory; |
| sfnt_stream->close = stream_close; |
| |
| FT_Stream_Free( |
| face->root.stream, |
| ( face->root.face_flags & FT_FACE_FLAG_EXTERNAL_STREAM ) != 0 ); |
| |
| face->root.stream = sfnt_stream; |
| face->root.face_flags &= ~FT_FACE_FLAG_EXTERNAL_STREAM; |
| |
| /* Set face_index to 0 or -1. */ |
| if ( *face_instance_index >= 0 ) |
| *face_instance_index = 0; |
| else |
| *face_instance_index = -1; |
| |
| FT_TRACE2(( "woff2_open_font: SFNT synthesized.\n" )); |
| |
| Exit: |
| FT_FREE( tables ); |
| FT_FREE( indices ); |
| FT_FREE( uncompressed_buf ); |
| FT_FREE( info.x_mins ); |
| |
| if ( woff2.ttc_fonts ) |
| { |
| WOFF2_TtcFont ttc_font = woff2.ttc_fonts; |
| |
| |
| for ( nn = 0; nn < woff2.num_fonts; nn++ ) |
| { |
| FT_FREE( ttc_font->table_indices ); |
| ttc_font++; |
| } |
| |
| FT_FREE( woff2.ttc_fonts ); |
| } |
| |
| if ( error ) |
| { |
| FT_FREE( sfnt ); |
| if ( sfnt_stream ) |
| { |
| FT_Stream_Close( sfnt_stream ); |
| FT_FREE( sfnt_stream ); |
| } |
| } |
| |
| return error; |
| } |
| |
| |
| #undef READ_255USHORT |
| #undef READ_BASE128 |
| #undef ROUND4 |
| #undef WRITE_USHORT |
| #undef WRITE_ULONG |
| #undef WRITE_SHORT |
| #undef WRITE_SFNT_BUF |
| #undef WRITE_SFNT_BUF_AT |
| |
| #undef N_CONTOUR_STREAM |
| #undef N_POINTS_STREAM |
| #undef FLAG_STREAM |
| #undef GLYPH_STREAM |
| #undef COMPOSITE_STREAM |
| #undef BBOX_STREAM |
| #undef INSTRUCTION_STREAM |
| |
| #else /* !FT_CONFIG_OPTION_USE_BROTLI */ |
| |
| /* ANSI C doesn't like empty source files */ |
| typedef int sfwoff2_dummy_; |
| |
| #endif /* !FT_CONFIG_OPTION_USE_BROTLI */ |
| |
| |
| /* END */ |