From 097cd874ee26ef254e6782b528c460a6f9b7d812 Mon Sep 17 00:00:00 2001 From: Dave Arnold Date: Thu, 15 Dec 2016 12:58:26 +0100 Subject: [PATCH] [truetype] Add `HVAR' table parsing. Note that this is not complete yet; it only handles advance width variation. Activation of the code follows in another commit. * include/freetype/ftmm.h (FT_Var_Named_Style): Add `psid' member. * src/truetype/ttgxvar.h (GX_HVarData, GX_AxisCoords, GX_HVarRegion, GX_HVStore, GX_WidthMap): New auxiliary structures for... (GX_HVarTable): ... HVAR main structure. (GX_BlendRec): Add data for HVAR loading. * src/truetype/ttgxvar.c (FT_FIXED_ONE, FT_fdot14ToFixed, FT_intToFixed, FT_fixedToInt): New macros. (ft_var_load_hvar): New function. (TT_Get_MM_Var): Updated. (tt_done_blend): Deallocate HVAR data. --- ChangeLog | 23 +++ include/freetype/ftmm.h | 1 + src/truetype/ttgxvar.c | 401 +++++++++++++++++++++++++++++++++++++++- src/truetype/ttgxvar.h | 76 ++++++++ 4 files changed, 498 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index d585d365d..26abc0832 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +2016-12-15 Dave Arnold + Werner Lemberg + + [truetype] Add `HVAR' table parsing. + + Note that this is not complete yet; it only handles advance width + variation. + + Activation of the code follows in another commit. + + * include/freetype/ftmm.h (FT_Var_Named_Style): Add `psid' member. + + * src/truetype/ttgxvar.h (GX_HVarData, GX_AxisCoords, GX_HVarRegion, + GX_HVStore, GX_WidthMap): New auxiliary structures for... + (GX_HVarTable): ... HVAR main structure. + (GX_BlendRec): Add data for HVAR loading. + + * src/truetype/ttgxvar.c (FT_FIXED_ONE, FT_fdot14ToFixed, + FT_intToFixed, FT_fixedToInt): New macros. + (ft_var_load_hvar): New function. + (TT_Get_MM_Var): Updated. + (tt_done_blend): Deallocate HVAR data. + 2016-12-15 Dave Arnold [cff] Extend number parsing. diff --git a/include/freetype/ftmm.h b/include/freetype/ftmm.h index 882bdbb05..a0238c5d2 100644 --- a/include/freetype/ftmm.h +++ b/include/freetype/ftmm.h @@ -171,6 +171,7 @@ FT_BEGIN_HEADER { FT_Fixed* coords; FT_UInt strid; + FT_UInt psid; /* since 2.7.1 */ } FT_Var_Named_Style; diff --git a/src/truetype/ttgxvar.c b/src/truetype/ttgxvar.c index 6213bfcde..8eb64546d 100644 --- a/src/truetype/ttgxvar.c +++ b/src/truetype/ttgxvar.c @@ -393,6 +393,361 @@ } + /* some macros we need */ + #define FT_FIXED_ONE ( (FT_Fixed)0x10000 ) + + #define FT_fdot14ToFixed( x ) \ + ( ( (FT_Fixed)( (FT_Int16)(x) ) ) << 2 ) + #define FT_intToFixed( i ) \ + ( (FT_Fixed)( (FT_UInt32)(i) << 16 ) ) + #define FT_fixedToInt( x ) \ + ( (FT_Short)( ( (FT_UInt32)(x) + 0x8000U ) >> 16 ) ) + + + /*************************************************************************/ + /* */ + /* */ + /* ft_var_load_hvar */ + /* */ + /* */ + /* Parse the `HVAR' table and set `blend->hvar_loaded' to TRUE. */ + /* */ + /* On success, `blend->hvar_checked' is set to TRUE. */ + /* */ + /* Some memory may remain allocated on error; it is always freed in */ + /* `tt_done_blend', however. */ + /* */ + /* */ + /* face :: The font face. */ + /* */ + /* */ + /* FreeType error code. 0 means success. */ + /* */ + static FT_Error + ft_var_load_hvar( TT_Face face ) + { + FT_Stream stream = FT_FACE_STREAM( face ); + FT_Memory memory = stream->memory; + + GX_Blend blend = face->blend; + + FT_Error error; + FT_UShort majorVersion; + FT_UShort minorVersion; + FT_ULong table_len; + FT_ULong table_offset; + FT_ULong store_offset; + FT_ULong map_offset; + + FT_ULong* dataOffsetArray = NULL; + + + blend->hvar_loaded = TRUE; + + FT_TRACE2(( "HVAR " )); + + error = face->goto_table( face, TTAG_HVAR, stream, &table_len ); + if ( error ) + { + FT_TRACE2(( "is missing\n" )); + goto Exit; + } + + table_offset = FT_STREAM_POS(); + + if ( FT_READ_USHORT( majorVersion ) || + FT_READ_USHORT( minorVersion ) ) + goto Exit; + if ( majorVersion != 1 ) + { + FT_TRACE2(( "bad table version %d\n", majorVersion )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + + if ( FT_READ_ULONG( store_offset ) || + FT_READ_ULONG( map_offset ) ) + goto Exit; + + /* parse item variation store */ + { + FT_UShort format; + FT_ULong region_offset; + FT_UInt i, j, k; + FT_UInt shortDeltaCount; + + GX_HVStore itemStore; + GX_HVarTable hvarTable; + GX_HVarData hvarData; + + + if ( FT_STREAM_SEEK( table_offset + store_offset ) || + FT_READ_USHORT( format ) ) + goto Exit; + if ( format != 1 ) + { + FT_TRACE2(( "bad store format %d\n", format )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + + if ( FT_NEW( blend->hvar_table ) ) /* allocate table at top level */ + goto Exit; + + hvarTable = blend->hvar_table; + itemStore = &hvarTable->itemStore; + + /* read top level fields */ + if ( FT_READ_ULONG( region_offset ) || + FT_READ_USHORT( itemStore->dataCount ) ) + goto Exit; + + /* make temporary copy of item variation data offsets; */ + /* we will parse region list first, then come back */ + if ( FT_NEW_ARRAY( dataOffsetArray, itemStore->dataCount ) ) + goto Exit; + + for ( i = 0; i < itemStore->dataCount; i++ ) + { + if ( FT_READ_ULONG( dataOffsetArray[i] ) ) + goto Exit; + } + + /* parse array of region records (region list) */ + if ( FT_STREAM_SEEK( table_offset + store_offset + region_offset ) ) + goto Exit; + + if ( FT_READ_USHORT( itemStore->axisCount ) || + FT_READ_USHORT( itemStore->regionCount ) ) + goto Exit; + + if ( FT_NEW_ARRAY( itemStore->varRegionList, itemStore->regionCount ) ) + goto Exit; + + for ( i = 0; i < itemStore->regionCount; i++ ) + { + GX_AxisCoords axisCoords; + + + if ( FT_NEW_ARRAY( itemStore->varRegionList[i].axisList, + itemStore->axisCount ) ) + goto Exit; + + axisCoords = itemStore->varRegionList[i].axisList; + + for ( j = 0; j < itemStore->axisCount; j++ ) + { + FT_Short start, peak, end; + + + if ( FT_READ_SHORT( start ) || + FT_READ_SHORT( peak ) || + FT_READ_SHORT( end ) ) + goto Exit; + + axisCoords[j].startCoord = FT_fdot14ToFixed( start ); + axisCoords[j].peakCoord = FT_fdot14ToFixed( peak ); + axisCoords[j].endCoord = FT_fdot14ToFixed( end ); + } + } + + /* end of region list parse */ + + /* use dataOffsetArray now to parse varData items */ + if ( FT_NEW_ARRAY( itemStore->varData, itemStore->dataCount ) ) + goto Exit; + + for ( i = 0; i < itemStore->dataCount; i++ ) + { + hvarData = &itemStore->varData[i]; + + if ( FT_STREAM_SEEK( table_offset + + store_offset + + dataOffsetArray[i] ) ) + goto Exit; + + if ( FT_READ_USHORT( hvarData->itemCount ) || + FT_READ_USHORT( shortDeltaCount ) || + FT_READ_USHORT( hvarData->regionIdxCount ) ) + goto Exit; + + /* check some data consistency */ + if ( shortDeltaCount > hvarData->regionIdxCount ) + { + FT_TRACE2(( "bad short count %d or region count %d\n", + shortDeltaCount, + hvarData->regionIdxCount )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + + if ( hvarData->regionIdxCount > itemStore->regionCount ) + { + FT_TRACE2(( "inconsistent regionCount %d in varData[%d]\n", + hvarData->regionIdxCount, + i )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + + /* parse region indices */ + if ( FT_NEW_ARRAY( hvarData->regionIndices, + hvarData->regionIdxCount ) ) + goto Exit; + + for ( j = 0; j < hvarData->regionIdxCount; j++ ) + { + if ( FT_READ_USHORT( hvarData->regionIndices[j] ) ) + goto Exit; + + if ( hvarData->regionIndices[j] >= itemStore->regionCount ) + { + FT_TRACE2(( "bad region index %d\n", + hvarData->regionIndices[j] )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + } + + /* Parse delta set. */ + /* */ + /* On input, deltas are ( shortDeltaCount + regionIdxCount ) bytes */ + /* each; on output, deltas are expanded to `regionIdxCount' shorts */ + /* each. */ + if ( FT_NEW_ARRAY( hvarData->deltaSet, + hvarData->regionIdxCount * hvarData->itemCount ) ) + goto Exit; + + /* the delta set is stored as a 2-dimensional array of shorts; */ + /* sign-extend signed bytes to signed shorts */ + for ( j = 0; j < hvarData->itemCount * hvarData->regionIdxCount; ) + { + for ( k = 0; k < shortDeltaCount; k++, j++ ) + { + /* read the short deltas */ + FT_Short delta; + + + if ( FT_READ_SHORT( delta ) ) + goto Exit; + + hvarData->deltaSet[j] = delta; + } + + for ( ; k < hvarData->regionIdxCount; k++, j++ ) + { + /* read the (signed) byte deltas */ + FT_Char delta; + + + if ( FT_READ_CHAR( delta ) ) + goto Exit; + + hvarData->deltaSet[j] = delta; + } + } + } + } + + /* end parse item variation store */ + + /* parse width map */ + { + GX_WidthMap widthMap; + + FT_UShort format; + FT_UInt entrySize; + FT_UInt innerBitCount; + FT_UInt innerIndexMask; + FT_UInt i, j; + + + widthMap = &blend->hvar_table->widthMap; + + if ( FT_READ_USHORT( format ) || + FT_READ_USHORT( widthMap->mapCount ) ) + goto Exit; + + if ( format & 0xFFC0 ) + { + FT_TRACE2(( "bad map format %d\n", format )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + + /* bytes per entry: 1, 2, 3, or 4 */ + entrySize = ( ( format & 0x0030 ) >> 4 ) + 1; + innerBitCount = ( format & 0x000F ) + 1; + innerIndexMask = ( 1 << innerBitCount ) - 1; + + if ( FT_NEW_ARRAY( widthMap->innerIndex, widthMap->mapCount ) ) + goto Exit; + + if ( FT_NEW_ARRAY( widthMap->outerIndex, widthMap->mapCount ) ) + goto Exit; + + for ( i = 0; i < widthMap->mapCount; i++ ) + { + FT_UInt mapData = 0; + FT_UInt outerIndex, innerIndex; + + + /* read map data one unsigned byte at a time, big endian */ + for ( j = 0; j < entrySize; j++ ) + { + FT_Byte data; + + + if ( FT_READ_BYTE( data ) ) + goto Exit; + + mapData = ( mapData << 8 ) | data; + } + + outerIndex = mapData >> innerBitCount; + + if ( outerIndex >= blend->hvar_table->itemStore.dataCount ) + { + FT_TRACE2(( "outerIndex[%d] == %d out of range\n", + i, + outerIndex )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + + widthMap->outerIndex[i] = outerIndex; + + innerIndex = mapData & innerIndexMask; + + if ( innerIndex >= + blend->hvar_table->itemStore.varData[outerIndex].itemCount ) + { + FT_TRACE2(( "innerIndex[%d] == %d out of range\n", + i, + innerIndex )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + + widthMap->innerIndex[i] = innerIndex; + } + } + + /* end parse width map */ + + FT_TRACE2(( "loaded\n" )); + error = FT_Err_Ok; + + Exit: + FT_FREE( dataOffsetArray ); + + if ( !error ) + blend->hvar_checked = TRUE; + + return error; + } + + typedef struct GX_GVar_Head_ { FT_Long version; @@ -778,6 +1133,7 @@ FT_Var_Axis* a; FT_Var_Named_Style* ns; GX_FVar_Head fvar_head; + FT_Bool usePsName; static const FT_Frame_Field fvar_fields[] = { @@ -851,8 +1207,6 @@ fvar_head.axisSize != 20 || /* axisCount limit implied by 16-bit instanceSize */ fvar_head.axisCount > 0x3FFE || - fvar_head.instanceSize != 4 + 4 * fvar_head.axisCount || - /* instanceCount limit implied by limited range of name IDs */ fvar_head.instanceCount > 0x7EFF || fvar_head.offsetToData + fvar_head.axisCount * 20U + fvar_head.instanceCount * fvar_head.instanceSize > table_len ) @@ -863,6 +1217,18 @@ goto Exit; } + if ( fvar_head.instanceSize == 4 + 4 * fvar_head.axisCount ) + usePsName = FALSE; + else if ( fvar_head.instanceSize == 6 + 4 * fvar_head.axisCount ) + usePsName = TRUE; + else + { + FT_TRACE1(( "\n" + "TT_Get_MM_Var: invalid `fvar' header\n" )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + FT_TRACE2(( "loaded\n" )); FT_TRACE5(( "number of GX style axes: %d\n", fvar_head.axisCount )); @@ -952,7 +1318,9 @@ ns = mmvar->namedstyle; for ( i = 0; i < fvar_head.instanceCount; i++, ns++ ) { - if ( FT_FRAME_ENTER( 4L + 4L * fvar_head.axisCount ) ) + /* PostScript names add 2 bytes to the instance record size */ + if ( FT_FRAME_ENTER( ( usePsName ? 6L : 4L ) + + 4L * fvar_head.axisCount ) ) goto Exit; ns->strid = FT_GET_USHORT(); @@ -961,6 +1329,9 @@ for ( j = 0; j < fvar_head.axisCount; j++ ) ns->coords[j] = FT_GET_LONG(); + if ( usePsName ) + ns->psid = FT_GET_USHORT(); + FT_FRAME_EXIT(); } } @@ -2296,6 +2667,30 @@ FT_FREE( blend->avar_segment ); } + if ( blend->hvar_table != NULL ) + { + if ( blend->hvar_table->itemStore.varData ) + { + for ( i = 0; i < blend->hvar_table->itemStore.dataCount; i++ ) + { + FT_FREE( blend->hvar_table->itemStore.varData[i].regionIndices ); + FT_FREE( blend->hvar_table->itemStore.varData[i].deltaSet ); + } + FT_FREE( blend->hvar_table->itemStore.varData ); + } + + if ( blend->hvar_table->itemStore.varRegionList ) + { + for ( i = 0; i < blend->hvar_table->itemStore.regionCount; i++ ) + FT_FREE( blend->hvar_table->itemStore.varRegionList[i].axisList ); + FT_FREE( blend->hvar_table->itemStore.varRegionList ); + } + + FT_FREE( blend->hvar_table->widthMap.innerIndex ); + FT_FREE( blend->hvar_table->widthMap.outerIndex ); + FT_FREE( blend->hvar_table ); + } + FT_FREE( blend->tuplecoords ); FT_FREE( blend->glyphoffsets ); FT_FREE( blend ); diff --git a/src/truetype/ttgxvar.h b/src/truetype/ttgxvar.h index 9e4d749c7..c5c98ead6 100644 --- a/src/truetype/ttgxvar.h +++ b/src/truetype/ttgxvar.h @@ -61,6 +61,77 @@ FT_BEGIN_HEADER } GX_AVarSegmentRec, *GX_AVarSegment; + typedef struct GX_HVarDataRec_ + { + FT_UInt itemCount; /* number of delta sets per item */ + FT_UInt regionIdxCount; /* number of region indices in this data */ + FT_UInt* regionIndices; /* array of `regionCount' indices; */ + /* these index `varRegionList' */ + FT_Short* deltaSet; /* array of `itemCount' deltas */ + /* use `innerIndex' for this array */ + + } GX_HVarDataRec, *GX_HVarData; + + + /* contribution of one axis to a region */ + typedef struct GX_AxisCoordsRec_ + { + FT_Fixed startCoord; + FT_Fixed peakCoord; /* zero means no effect (factor = 1) */ + FT_Fixed endCoord; + + } GX_AxisCoordsRec, *GX_AxisCoords; + + + typedef struct GX_HVarRegionRec_ + { + GX_AxisCoords axisList; /* array of axisCount records */ + + } GX_HVarRegionRec, *GX_HVarRegion; + + + /* HVAR item variation store */ + typedef struct GX_HVStoreRec_ + { + FT_UInt dataCount; + GX_HVarData varData; /* array of dataCount records; */ + /* use `outerIndex' for this array */ + FT_UShort axisCount; + FT_UInt regionCount; /* total number of regions defined */ + GX_HVarRegion varRegionList; + + } GX_HVStoreRec, *GX_HVStore; + + + typedef struct GX_WidthMapRec_ + { + FT_UInt mapCount; + FT_UInt* outerIndex; /* indices to item var data */ + FT_UInt* innerIndex; /* indices to delta set */ + + } GX_WidthMapRec, *GX_WidthMap; + + + /*************************************************************************/ + /* */ + /* */ + /* GX_HVarTableRec */ + /* */ + /* */ + /* Data from the `HVAR' table. */ + /* */ + typedef struct GX_HVarTableRec_ + { + GX_HVStoreRec itemStore; /* Item Variation Store */ + GX_WidthMapRec widthMap; /* Advance Width Mapping */ +#if 0 + GX_LSBMap LsbMap; /* not implemented */ + GX_RSBMap RsbMap; /* not implemented */ +#endif + + } GX_HVarTableRec, *GX_HVarTable; + + /*************************************************************************/ /* */ /* */ @@ -89,6 +160,11 @@ FT_BEGIN_HEADER FT_Bool avar_checked; GX_AVarSegment avar_segment; + FT_Bool hvar_loaded; + FT_Bool hvar_checked; + FT_Error hvar_error; + GX_HVarTable hvar_table; + FT_UInt tuplecount; /* shared tuples in `gvar' */ FT_Fixed* tuplecoords; /* tuplecoords[tuplecount][num_axis] */