mirror of https://github.com/freetype/freetype
updated version of the experimental Type 1 driver
(this thing now works even better than the "regular" driver, but is much smaller). Provides no hinter !!
This commit is contained in:
parent
861ba624db
commit
95bec28220
|
@ -108,16 +108,16 @@ T1Z_COMPILE := $(FT_COMPILE) $(T1Z_INCLUDE:%=$I%)
|
|||
T1Z_DRV_SRC := $(T1Z_DIR_)t1parse.c \
|
||||
$(T1Z_DIR_)t1load.c \
|
||||
$(T1Z_DIR_)t1driver.c \
|
||||
$(T1Z_DIR_)t1encode.c \
|
||||
$(T1Z_DIR_)t1afm.c \
|
||||
$(T1Z_DIR_)t1gload.c
|
||||
|
||||
|
||||
# Type1 driver headers
|
||||
#
|
||||
T1Z_DRV_H := $(T1Z_DIR_)t1errors.h \
|
||||
$(T1Z_DIR_)t1config.h \
|
||||
$(T1SHARED_H) \
|
||||
$(T1Z_DRV_SRC:%.c=%.h)
|
||||
$(T1Z_DIR_)t1config.h \
|
||||
$(T1SHARED_H) \
|
||||
$(T1Z_DRV_SRC:%.c=%.h)
|
||||
|
||||
|
||||
# driver object(s)
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
/***************************************************************************
|
||||
*
|
||||
* t1afm.c - support for reading Type 1 AFM files
|
||||
*
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#include <t1afm.h>
|
||||
#include <ftstream.h>
|
||||
#include <t1types.h>
|
||||
#include <stdlib.h> /* for qsort */
|
||||
|
||||
LOCAL_FUNC
|
||||
void T1_Done_AFM( FT_Memory memory, T1_AFM* afm )
|
||||
{
|
||||
FREE( afm->kern_pairs );
|
||||
afm->num_pairs = 0;
|
||||
}
|
||||
|
||||
#undef IS_KERN_PAIR
|
||||
#define IS_KERN_PAIR(p) ( p[0] == 'K' && p[1] == 'P' )
|
||||
|
||||
#define IS_ALPHANUM(c) ( (c >= 'A' && c <= 'Z') || \
|
||||
(c >= 'a' && c <= 'z') || \
|
||||
(c >= '0' && c <= '9') || \
|
||||
(c == '_' && c == '.') )
|
||||
|
||||
/* read a glyph name and return the equivalent glyph index */
|
||||
static
|
||||
FT_UInt afm_atoindex( FT_Byte* *start, FT_Byte* limit, T1_Font* type1 )
|
||||
{
|
||||
FT_Byte* p = *start;
|
||||
FT_Int len;
|
||||
FT_UInt result = 0;
|
||||
char temp[64];
|
||||
|
||||
/* skip whitespace */
|
||||
while ( (*p == ' ' || *p == '\t' || *p == ':' || *p == ';') && p < limit )
|
||||
p++;
|
||||
*start = p;
|
||||
|
||||
/* now, read glyph name */
|
||||
while ( IS_ALPHANUM(*p) && p < limit ) p++;
|
||||
len = p - *start;
|
||||
if (len > 0 && len < 64)
|
||||
{
|
||||
FT_Int n;
|
||||
|
||||
/* copy glyph name to intermediate array */
|
||||
MEM_Copy( temp, start, len );
|
||||
temp[len] = 0;
|
||||
|
||||
/* lookup glyph name in face array */
|
||||
for ( n = 0; n < type1->num_glyphs; n++ )
|
||||
{
|
||||
char* gname = (char*)type1->glyph_names;
|
||||
|
||||
if ( gname && gname[0] == temp[0] && strcmp(gname,temp) == 0 )
|
||||
{
|
||||
result = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*start = p;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* read an integer */
|
||||
static
|
||||
int afm_atoi( FT_Byte** start, FT_Byte* limit )
|
||||
{
|
||||
FT_Byte* p = *start;
|
||||
int sum = 0;
|
||||
|
||||
/* skip everything that is not a number */
|
||||
while ( p < limit && (*p < '0' || *p > '9') )
|
||||
p++;
|
||||
|
||||
while ( p < limit && (*p >= '0' || *p < '9') )
|
||||
{
|
||||
sum = sum*10 + (*p - '0');
|
||||
p++;
|
||||
}
|
||||
*start = p;
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
#undef KERN_INDEX
|
||||
#define KERN_INDEX(g1,g2) (((T1_ULong)g1 << 16) | g2)
|
||||
|
||||
/* compare two kerning pairs */
|
||||
static
|
||||
int compare_kern_pairs( const void* a, const void* b )
|
||||
{
|
||||
T1_Kern_Pair* pair1 = (T1_Kern_Pair*)a;
|
||||
T1_Kern_Pair* pair2 = (T1_Kern_Pair*)b;
|
||||
|
||||
T1_ULong index1 = KERN_INDEX(pair1->glyph1,pair1->glyph2);
|
||||
T1_ULong index2 = KERN_INDEX(pair2->glyph1,pair2->glyph2);
|
||||
|
||||
return ( index1 < index2 ? -1 :
|
||||
( index1 > index2 ? 1 : 0 ));
|
||||
}
|
||||
|
||||
|
||||
/* parse an AFM file - for now, only read the kerning pairs */
|
||||
LOCAL_FUNC
|
||||
FT_Error T1_Read_AFM( FT_Stream stream,
|
||||
FT_Face t1_face )
|
||||
{
|
||||
FT_Error error;
|
||||
FT_Memory memory = stream->memory;
|
||||
FT_Byte* start;
|
||||
FT_Byte* limit;
|
||||
FT_Byte* p;
|
||||
FT_Int count = 0;
|
||||
T1_Kern_Pair* pair;
|
||||
T1_Font* type1 = &((T1_Face)t1_face)->type1;
|
||||
T1_AFM* afm = 0;
|
||||
|
||||
if ( !ACCESS_Frame(stream->size) )
|
||||
return error;
|
||||
|
||||
start = (FT_Byte*)stream->cursor;
|
||||
limit = (FT_Byte*)stream->limit;
|
||||
p = start;
|
||||
|
||||
/* we are now going to count the occurences of "KP" or "KPX" in */
|
||||
/* the AFM file.. */
|
||||
count = 0;
|
||||
for ( p = start; p < limit-3; p++ )
|
||||
{
|
||||
if ( IS_KERN_PAIR(p) )
|
||||
count++;
|
||||
}
|
||||
|
||||
/* Actually, kerning pairs are simply optional !! */
|
||||
if (count == 0)
|
||||
goto Exit;
|
||||
|
||||
/* allocate the pairs */
|
||||
if ( ALLOC( afm, sizeof(*afm ) ) ||
|
||||
ALLOC_ARRAY( afm->kern_pairs, count, T1_Kern_Pair ) )
|
||||
goto Exit;
|
||||
|
||||
/* now, read each kern pair */
|
||||
pair = afm->kern_pairs;
|
||||
afm->num_pairs = count;
|
||||
|
||||
/* save in face object */
|
||||
((T1_Face)t1_face)->afm_data = afm;
|
||||
|
||||
for ( p = start; p < limit-3; p++ )
|
||||
{
|
||||
if ( IS_KERN_PAIR(p) )
|
||||
{
|
||||
FT_Byte* q;
|
||||
|
||||
/* skip keyword (KP or KPX) */
|
||||
q = p+2;
|
||||
if (*q == 'X') q++;
|
||||
|
||||
pair->glyph1 = afm_atoindex( &q, limit, type1 );
|
||||
pair->glyph2 = afm_atoindex( &q, limit, type1 );
|
||||
pair->kerning.x = afm_atoi( &q, limit );
|
||||
|
||||
pair->kerning.y = 0;
|
||||
if ( p[2] != 'X' )
|
||||
pair->kerning.y = afm_atoi( &q, limit );
|
||||
|
||||
pair++;
|
||||
}
|
||||
}
|
||||
|
||||
/* now, sort the kern pairs according to their glyph indices */
|
||||
qsort( afm->kern_pairs, count, sizeof(T1_Kern_Pair), compare_kern_pairs );
|
||||
|
||||
Exit:
|
||||
if (error)
|
||||
FREE( afm );
|
||||
|
||||
FORGET_Frame();
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/* find the kerning for a given glyph pair */
|
||||
LOCAL_FUNC
|
||||
void T1_Get_Kerning( T1_AFM* afm,
|
||||
FT_UInt glyph1,
|
||||
FT_UInt glyph2,
|
||||
FT_Vector* kerning )
|
||||
{
|
||||
T1_Kern_Pair *min, *mid, *max;
|
||||
T1_ULong index = KERN_INDEX(glyph1,glyph2);
|
||||
|
||||
/* simple binary search */
|
||||
min = afm->kern_pairs;
|
||||
max = min + afm->num_pairs-1;
|
||||
|
||||
while (min <= max)
|
||||
{
|
||||
T1_ULong midi;
|
||||
|
||||
mid = min + (max-min)/2;
|
||||
midi = KERN_INDEX(mid->glyph1,mid->glyph2);
|
||||
if ( midi == index )
|
||||
{
|
||||
*kerning = mid->kerning;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( midi < index ) min = mid+1;
|
||||
else max = mid-1;
|
||||
}
|
||||
kerning->x = 0;
|
||||
kerning->y = 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/***************************************************************************
|
||||
*
|
||||
* t1afm.h - support for reading Type 1 AFM files
|
||||
*
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef T1AFM_H
|
||||
#define T1AFM_H
|
||||
|
||||
#include <ftobjs.h>
|
||||
|
||||
/* In this version, we only read the kerning table from the */
|
||||
/* AFM file. We may add support for ligatures a bit later.. */
|
||||
|
||||
typedef struct T1_Kern_Pair_
|
||||
{
|
||||
FT_UInt glyph1;
|
||||
FT_UInt glyph2;
|
||||
FT_Vector kerning;
|
||||
|
||||
} T1_Kern_Pair;
|
||||
|
||||
|
||||
typedef struct T1_AFM_
|
||||
{
|
||||
FT_Int num_pairs;
|
||||
T1_Kern_Pair* kern_pairs;
|
||||
|
||||
} T1_AFM;
|
||||
|
||||
|
||||
LOCAL_DEF
|
||||
FT_Error T1_Read_AFM( FT_Stream stream,
|
||||
FT_Face face );
|
||||
|
||||
LOCAL_DEF
|
||||
void T1_Done_AFM( FT_Memory memory,
|
||||
T1_AFM* afm );
|
||||
|
||||
LOCAL_DEF
|
||||
void T1_Get_Kerning( T1_AFM* afm,
|
||||
FT_UInt glyph1,
|
||||
FT_UInt glyph2,
|
||||
FT_Vector* kerning );
|
||||
|
||||
#endif /* T1AFM_H */
|
|
@ -42,4 +42,11 @@
|
|||
/* */
|
||||
#undef T1_CONFIG_OPTION_DISABLE_HINTER
|
||||
|
||||
/* Define this configuration macro if you want to prevent the */
|
||||
/* compilation of "t1afm", which is in charge of reading Type1 */
|
||||
/* AFM files into an existing face. Note that when set, the T1 */
|
||||
/* driver will be unable to produce kerning distances.. */
|
||||
/* */
|
||||
#undef T1_CONFIG_OPTION_NO_AFM
|
||||
|
||||
#endif /* T1CONFIG_H */
|
||||
|
|
|
@ -17,13 +17,16 @@
|
|||
|
||||
#include <t1driver.h>
|
||||
#include <t1gload.h>
|
||||
#include <t1afm.h>
|
||||
|
||||
#include <ftdebug.h>
|
||||
#include <ftstream.h>
|
||||
#include <psnames.h>
|
||||
|
||||
#undef FT_COMPONENT
|
||||
#define FT_COMPONENT trace_t1driver
|
||||
|
||||
#ifndef T1_CONFIG_OPTION_NO_AFM
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Function> */
|
||||
|
@ -52,15 +55,68 @@
|
|||
/* time). */
|
||||
/* */
|
||||
static
|
||||
void* Get_Interface( FT_Driver* driver,
|
||||
const FT_String* interface )
|
||||
FTDriver_Interface Get_Interface( FT_Driver driver,
|
||||
const FT_String* interface )
|
||||
{
|
||||
UNUSED(driver);
|
||||
UNUSED(interface);
|
||||
if ( strcmp( (const char*)interface, "attach_file" ) == 0 )
|
||||
return (FTDriver_Interface)T1_Read_AFM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Function> */
|
||||
/* Get_Kerning */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* A driver method used to return the kerning vector between two */
|
||||
/* glyphs of the same face. */
|
||||
/* */
|
||||
/* <Input> */
|
||||
/* face :: A handle to the source face object. */
|
||||
/* */
|
||||
/* left_glyph :: The index of the left glyph in the kern pair. */
|
||||
/* */
|
||||
/* right_glyph :: The index of the right glyph in the kern pair. */
|
||||
/* */
|
||||
/* <Output> */
|
||||
/* kerning :: The kerning vector. This is in font units for */
|
||||
/* scalable formats, and in pixels for fixed-sizes */
|
||||
/* formats. */
|
||||
/* */
|
||||
/* <Return> */
|
||||
/* FreeType error code. 0 means success. */
|
||||
/* */
|
||||
/* <Note> */
|
||||
/* Only horizontal layouts (left-to-right & right-to-left) are */
|
||||
/* supported by this function. Other layouts, or more sophisticated */
|
||||
/* kernings are out of scope of this method (the basic driver */
|
||||
/* interface is meant to be simple). */
|
||||
/* */
|
||||
/* They can be implemented by format-specific interfaces. */
|
||||
/* */
|
||||
static
|
||||
T1_Error Get_Kerning( T1_Face face,
|
||||
T1_UInt left_glyph,
|
||||
T1_UInt right_glyph,
|
||||
T1_Vector* kerning )
|
||||
{
|
||||
T1_AFM* afm;
|
||||
|
||||
kerning->x = 0;
|
||||
kerning->y = 0;
|
||||
|
||||
afm = (T1_AFM*)face->afm_data;
|
||||
if (afm)
|
||||
T1_Get_Kerning( afm, left_glyph, right_glyph, kerning );
|
||||
|
||||
return T1_Err_Ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
/******************************************************************/
|
||||
/* */
|
||||
/* <Function> Set_Char_Sizes */
|
||||
|
@ -130,6 +186,171 @@
|
|||
return T1_Reset_Size(size);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Function> */
|
||||
/* Get_Char_Index */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* Uses a charmap to return a given character code's glyph index. */
|
||||
/* */
|
||||
/* <Input> */
|
||||
/* charmap :: A handle to the source charmap object. */
|
||||
/* charcode :: The character code. */
|
||||
/* */
|
||||
/* <Return> */
|
||||
/* Glyph index. 0 means `undefined character code'. */
|
||||
/* */
|
||||
static
|
||||
T1_UInt Get_Char_Index( FT_CharMap charmap,
|
||||
T1_Long charcode )
|
||||
{
|
||||
T1_Face face;
|
||||
T1_UInt result = 0;
|
||||
PSNames_Interface* psnames;
|
||||
|
||||
face = (T1_Face)charmap->face;
|
||||
psnames = (PSNames_Interface*)face->psnames;
|
||||
if (psnames)
|
||||
switch (charmap->encoding)
|
||||
{
|
||||
/********************************************************************/
|
||||
/* */
|
||||
/* Unicode encoding support */
|
||||
/* */
|
||||
case ft_encoding_unicode:
|
||||
{
|
||||
/* use the "psnames" module to synthetize the Unicode charmap */
|
||||
result = psnames->lookup_unicode( &face->unicode_map,
|
||||
(T1_ULong)charcode );
|
||||
|
||||
/* the function returns 0xFFFF when the Unicode charcode has */
|
||||
/* no corresponding glyph.. */
|
||||
if (result == 0xFFFF)
|
||||
result = 0;
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
/********************************************************************/
|
||||
/* */
|
||||
/* Custom Type 1 encoding */
|
||||
/* */
|
||||
case ft_encoding_adobe_custom:
|
||||
{
|
||||
T1_Encoding* encoding = &face->type1.encoding;
|
||||
if (charcode >= encoding->code_first &&
|
||||
charcode <= encoding->code_last)
|
||||
{
|
||||
result = encoding->char_index[charcode];
|
||||
}
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
/********************************************************************/
|
||||
/* */
|
||||
/* Adobe Standard & Expert encoding support */
|
||||
/* */
|
||||
default:
|
||||
if (charcode < 256)
|
||||
{
|
||||
FT_UInt code;
|
||||
FT_Int n;
|
||||
const char* glyph_name;
|
||||
|
||||
code = psnames->adobe_std_encoding[charcode];
|
||||
if (charmap->encoding == ft_encoding_adobe_expert)
|
||||
code = psnames->adobe_expert_encoding[charcode];
|
||||
|
||||
glyph_name = psnames->adobe_std_strings(code);
|
||||
if (!glyph_name) break;
|
||||
|
||||
for ( n = 0; n < face->type1.num_glyphs; n++ )
|
||||
{
|
||||
const char* gname = face->type1.glyph_names[n];
|
||||
|
||||
if ( gname && gname[0] == glyph_name[0] &&
|
||||
strcmp( gname, glyph_name ) == 0 )
|
||||
{
|
||||
result = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Exit:
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
T1_Error Init_Face( FT_Stream stream,
|
||||
FT_Int face_index,
|
||||
T1_Face face )
|
||||
{
|
||||
T1_Error error;
|
||||
|
||||
error = T1_Init_Face(stream, face_index, face);
|
||||
if (!error)
|
||||
{
|
||||
FT_Face root = &face->root;
|
||||
FT_CharMap charmap = face->charmaprecs;
|
||||
|
||||
/* synthetize a Unicode charmap if there is support in the "psnames" */
|
||||
/* module.. */
|
||||
if (face->psnames)
|
||||
{
|
||||
PSNames_Interface* psnames = (PSNames_Interface*)face->psnames;
|
||||
if (psnames->unicode_value)
|
||||
{
|
||||
error = psnames->build_unicodes( root->memory,
|
||||
face->type1.num_glyphs,
|
||||
(const char**)face->type1.glyph_names,
|
||||
&face->unicode_map );
|
||||
if (!error)
|
||||
{
|
||||
root->charmap = charmap;
|
||||
charmap->face = (FT_Face)face;
|
||||
charmap->encoding = ft_encoding_unicode;
|
||||
charmap->platform_id = 3;
|
||||
charmap->encoding_id = 1;
|
||||
charmap++;
|
||||
}
|
||||
|
||||
/* simply clear the error in case of failure (which really) */
|
||||
/* means that out of memory or no unicode glyph names */
|
||||
error = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* now, support either the standard, expert, or custom encodings */
|
||||
charmap->face = (FT_Face)face;
|
||||
charmap->platform_id = 7; /* a new platform id for Adobe fonts ?? */
|
||||
|
||||
switch (face->type1.encoding_type)
|
||||
{
|
||||
case t1_encoding_standard:
|
||||
charmap->encoding = ft_encoding_adobe_standard;
|
||||
charmap->encoding_id = 0;
|
||||
break;
|
||||
|
||||
case t1_encoding_expert:
|
||||
charmap->encoding = ft_encoding_adobe_expert;
|
||||
charmap->encoding_id = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
charmap->encoding = ft_encoding_adobe_custom;
|
||||
charmap->encoding_id = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
root->charmaps = face->charmaps;
|
||||
root->num_charmaps = charmap - face->charmaprecs + 1;
|
||||
face->charmaps[0] = &face->charmaprecs[0];
|
||||
face->charmaps[1] = &face->charmaprecs[1];
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/******************************************************************/
|
||||
/* */
|
||||
|
@ -206,7 +427,7 @@
|
|||
/* */
|
||||
|
||||
EXPORT_FUNC
|
||||
const FT_DriverInterface t1_driver_interface =
|
||||
const FT_DriverInterface t1z_driver_interface =
|
||||
{
|
||||
sizeof( FT_DriverRec ),
|
||||
sizeof( T1_FaceRec ),
|
||||
|
@ -214,18 +435,28 @@
|
|||
sizeof( T1_GlyphSlotRec ),
|
||||
|
||||
"type1",
|
||||
100, /* driver version == 1.0 */
|
||||
200, /* requires FreeType 2.0 or above */
|
||||
100,
|
||||
200,
|
||||
|
||||
0, /* format interface */
|
||||
|
||||
(FTDriver_initDriver) T1_Init_Driver,
|
||||
(FTDriver_doneDriver) T1_Done_Driver,
|
||||
(FTDriver_getInterface) Get_Interface,
|
||||
|
||||
(FTDriver_initFace) T1_Init_Face,
|
||||
#ifdef T1_CONFIG_OPTION_NO_AFM
|
||||
(FTDriver_getInterface) 0,
|
||||
#else
|
||||
(FTDriver_getInterface) Get_Interface,
|
||||
#endif
|
||||
|
||||
(FTDriver_initFace) Init_Face,
|
||||
(FTDriver_doneFace) T1_Done_Face,
|
||||
|
||||
#ifdef T1_CONFIG_OPTION_NO_AFM
|
||||
(FTDriver_getKerning) 0,
|
||||
#else
|
||||
(FTDriver_getKerning) Get_Kerning,
|
||||
#endif
|
||||
|
||||
(FTDriver_initSize) T1_Init_Size,
|
||||
(FTDriver_doneSize) T1_Done_Size,
|
||||
|
@ -236,36 +467,38 @@
|
|||
(FTDriver_doneGlyphSlot) T1_Done_GlyphSlot,
|
||||
(FTDriver_loadGlyph) T1_Load_Glyph,
|
||||
|
||||
(FTDriver_getCharIndex) 0,
|
||||
(FTDriver_getCharIndex) Get_Char_Index,
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* */
|
||||
/* <Function> */
|
||||
/* getDriverInterface */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* This function is used when compiling the font driver as a */
|
||||
/* shared library (`.DLL' or `.so'). It will be used by the */
|
||||
/* high-level library of FreeType to retrieve the address of the */
|
||||
/* driver's generic interface. */
|
||||
/* */
|
||||
/* It shouldn't be implemented in a static build, as each driver must */
|
||||
/* have the same function as an exported entry point. */
|
||||
/* */
|
||||
/* <Return> */
|
||||
/* The address of the TrueType's driver generic interface. The */
|
||||
/* format-specific interface can then be retrieved through the method */
|
||||
/* interface->get_format_interface. */
|
||||
/* */
|
||||
#ifdef FT_CONFIG_OPTION_DYNAMIC_DRIVERS
|
||||
|
||||
/******************************************************************/
|
||||
/* */
|
||||
/* <Function> Get_FreeType_Driver_Interface */
|
||||
/* */
|
||||
/* <Description> */
|
||||
/* This function is used when compiling the TrueType driver */
|
||||
/* as a shared library (.DLL or .so). It will be used by the */
|
||||
/* high-level library of FreeType to retrieve the address of */
|
||||
/* the driver's generic interface. */
|
||||
/* */
|
||||
/* It shouldn't be implemented in a static build, as each */
|
||||
/* driver must have the same function as an exported entry */
|
||||
/* point. */
|
||||
/* */
|
||||
/* <Return> */
|
||||
/* address of TrueType's driver generic interface. The */
|
||||
/* forma-specific interface can then be retrieved through */
|
||||
/* the method interface->get_format_interface.. */
|
||||
/* */
|
||||
|
||||
#ifdef FT_CONFIG_OPTION_DYNAMIC_DRIVERS
|
||||
|
||||
EXPORT_FUNC
|
||||
FT_DriverInterface* getDriverInterface( void )
|
||||
{
|
||||
return &t1_driver_interface;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_OPTION_DYNAMIC_DRIVERS */
|
||||
|
||||
#endif /* FT_CONFIG_OPTION_DYNAMIC_DRIVERS */
|
||||
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include <t1errors.h>
|
||||
|
||||
EXPORT_DEF
|
||||
const FT_DriverInterface t1_driver_interface;
|
||||
const FT_DriverInterface t1z_driver_interface;
|
||||
|
||||
#endif /* T1DRIVER_H */
|
||||
|
||||
|
|
|
@ -17,9 +17,75 @@
|
|||
|
||||
#include <t1gload.h>
|
||||
#include <ftdebug.h>
|
||||
#include <t1encode.h>
|
||||
#include <ftstream.h>
|
||||
|
||||
#undef FT_COMPONENT
|
||||
#define FT_COMPONENT trace_t1gload
|
||||
|
||||
typedef enum T1_Operator_
|
||||
{
|
||||
op_none = 0,
|
||||
op_endchar,
|
||||
op_hsbw,
|
||||
op_seac,
|
||||
op_sbw,
|
||||
op_closepath,
|
||||
op_hlineto,
|
||||
op_hmoveto,
|
||||
op_hvcurveto,
|
||||
op_rlineto,
|
||||
op_rmoveto,
|
||||
op_rrcurveto,
|
||||
op_vhcurveto,
|
||||
op_vlineto,
|
||||
op_vmoveto,
|
||||
op_dotsection,
|
||||
op_hstem,
|
||||
op_hstem3,
|
||||
op_vstem,
|
||||
op_vstem3,
|
||||
op_div,
|
||||
op_callothersubr,
|
||||
op_callsubr,
|
||||
op_pop,
|
||||
op_return,
|
||||
op_setcurrentpoint,
|
||||
|
||||
op_max /* never remove this one */
|
||||
|
||||
} T1_Operator;
|
||||
|
||||
static const T1_Int t1_args_count[ op_max ] =
|
||||
{
|
||||
0, /* none */
|
||||
0, /* endchar */
|
||||
2, /* hsbw */
|
||||
5, /* seac */
|
||||
4, /* sbw */
|
||||
0, /* closepath */
|
||||
1, /* hlineto */
|
||||
1, /* hmoveto */
|
||||
4, /* hvcurveto */
|
||||
2, /* rlineto */
|
||||
2, /* rmoveto */
|
||||
6, /* rrcurveto */
|
||||
4, /* vhcurveto */
|
||||
1, /* vlineto */
|
||||
1, /* vmoveto */
|
||||
0, /* dotsection */
|
||||
2, /* hstem */
|
||||
6, /* hstem3 */
|
||||
2, /* vstem */
|
||||
6, /* vstem3 */
|
||||
2, /* div */
|
||||
-1, /* callothersubr */
|
||||
1, /* callsubr */
|
||||
0, /* pop */
|
||||
0, /* return */
|
||||
2 /* setcurrentpoint */
|
||||
};
|
||||
|
||||
|
||||
/**********************************************************************/
|
||||
/**********************************************************************/
|
||||
/**********************************************************************/
|
||||
|
@ -264,17 +330,26 @@
|
|||
if (outline->n_contours > 0)
|
||||
outline->contours[ outline->n_contours-1 ] = outline->n_points-1;
|
||||
|
||||
outline->n_contours++;
|
||||
return T1_Err_Ok;
|
||||
}
|
||||
|
||||
|
||||
/* if a path was begun, add its first on-curve point */
|
||||
static
|
||||
T1_Error start_point( T1_Builder* builder,
|
||||
T1_Pos x,
|
||||
T1_Pos y )
|
||||
{
|
||||
return builder->path_begun && add_point1( builder, x, y );
|
||||
/* test wether we're building a new contour */
|
||||
if (!builder->path_begun)
|
||||
{
|
||||
T1_Error error;
|
||||
|
||||
builder->path_begun = 1;
|
||||
error = add_contour( builder );
|
||||
if (error) return error;
|
||||
}
|
||||
return add_point1( builder, x, y );
|
||||
}
|
||||
|
||||
|
||||
|
@ -312,20 +387,22 @@
|
|||
T1_Int lookup_glyph_by_stdcharcode( T1_Face face,
|
||||
T1_Int charcode )
|
||||
{
|
||||
T1_Int n;
|
||||
const T1_String* glyph_name;
|
||||
T1_Int n;
|
||||
const T1_String* glyph_name;
|
||||
PSNames_Interface* psnames = (PSNames_Interface*)face->psnames;
|
||||
|
||||
/* check range of standard char code */
|
||||
if (charcode < 0 || charcode > 255)
|
||||
return -1;
|
||||
|
||||
glyph_name = t1_standard_strings[t1_standard_encoding[charcode]];
|
||||
glyph_name = psnames->adobe_std_strings(
|
||||
psnames->adobe_std_encoding[charcode]);
|
||||
|
||||
for ( n = 0; n < face->type1.num_glyphs; n++ )
|
||||
{
|
||||
T1_String* name = (T1_String*)face->type1.glyph_names[n];
|
||||
|
||||
if ( name && name[0] == glyph_name[0] && strcmp(name,glyph_name) == 0 )
|
||||
if ( name && strcmp(name,glyph_name) == 0 )
|
||||
return n;
|
||||
}
|
||||
|
||||
|
@ -506,62 +583,364 @@
|
|||
while ( ip < limit )
|
||||
{
|
||||
T1_Int* top = decoder->top;
|
||||
T1_Operator op = op_none;
|
||||
T1_Long value = 0;
|
||||
|
||||
/********************************************************************/
|
||||
/* */
|
||||
/* Decode operator or operand */
|
||||
/* */
|
||||
/* */
|
||||
|
||||
/* First of all, decompress operator or value */
|
||||
switch (*ip++)
|
||||
{
|
||||
case 1: /* hstem */
|
||||
case 3: /* vstem */
|
||||
case 1: op = op_hstem; break;
|
||||
|
||||
case 3: op = op_vstem; break;
|
||||
case 4: op = op_vmoveto; break;
|
||||
case 5: op = op_rlineto; break;
|
||||
case 6: op = op_hlineto; break;
|
||||
case 7: op = op_vlineto; break;
|
||||
case 8: op = op_rrcurveto; break;
|
||||
case 9: op = op_closepath; break;
|
||||
case 10: op = op_callsubr; break;
|
||||
case 11: op = op_return; break;
|
||||
|
||||
case 13: op = op_hsbw; break;
|
||||
case 14: op = op_endchar; break;
|
||||
|
||||
case 21: op = op_rmoveto; break;
|
||||
case 22: op = op_hmoveto; break;
|
||||
|
||||
case 30: op = op_vhcurveto; break;
|
||||
case 31: op = op_hvcurveto; break;
|
||||
|
||||
case 12:
|
||||
{
|
||||
Clear_Stack:
|
||||
top = decoder->stack;
|
||||
if (ip > limit)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : invalid escape (12+EOF)\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
switch (*ip++)
|
||||
{
|
||||
case 0: op = op_dotsection; break;
|
||||
case 1: op = op_vstem3; break;
|
||||
case 2: op = op_hstem3; break;
|
||||
case 6: op = op_seac; break;
|
||||
case 7: op = op_sbw; break;
|
||||
case 12: op = op_div; break;
|
||||
case 16: op = op_callothersubr; break;
|
||||
case 17: op = op_pop; break;
|
||||
case 33: op = op_setcurrentpoint; break;
|
||||
|
||||
default:
|
||||
FT_ERROR(( "T1.Parse_CharStrings : invalid escape (12+%d)\n",
|
||||
ip[-1] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 255: /* four bytes integer */
|
||||
{
|
||||
if (ip+4 > limit)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : unexpected EOF in integer\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
value = ((long)ip[0] << 24) |
|
||||
((long)ip[1] << 16) |
|
||||
((long)ip[2] << 8) |
|
||||
ip[3];
|
||||
ip += 4;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (ip[-1] >= 32)
|
||||
{
|
||||
if (ip[-1] < 247)
|
||||
value = (long)ip[-1] - 139;
|
||||
else
|
||||
{
|
||||
if (++ip > limit)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : unexpected EOF in integer\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
if (ip[-2] < 251)
|
||||
value = ((long)(ip[-2]-247) << 8) + ip[-1] + 108;
|
||||
else
|
||||
value = -((((long)ip[-2]-251) << 8) + ip[-1] + 108 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : invalid byte (%d)\n",
|
||||
ip[-1] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************************/
|
||||
/* */
|
||||
/* Push value on stack, or process operator */
|
||||
/* */
|
||||
/* */
|
||||
if ( op == op_none )
|
||||
{
|
||||
if ( top - decoder->stack >= T1_MAX_CHARSTRINGS_OPERANDS )
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : Stack overflow !!\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
FT_TRACE4(( " %ld", value ));
|
||||
*top++ = value;
|
||||
decoder->top = top;
|
||||
}
|
||||
else if ( op == op_callothersubr ) /* callothersubr */
|
||||
{
|
||||
FT_TRACE4(( " callothersubr" ));
|
||||
if ( top - decoder->stack < 2 )
|
||||
goto Stack_Underflow;
|
||||
|
||||
top -= 2;
|
||||
switch ( top[1] )
|
||||
{
|
||||
case 1: /* start flex feature ---------------------- */
|
||||
{
|
||||
if ( top[0] != 0 ) goto Unexpected_OtherSubr;
|
||||
|
||||
decoder->flex_state = 1;
|
||||
decoder->num_flex_vectors = 0;
|
||||
if ( start_point(builder, x, y) ||
|
||||
check_points(builder,6) ) goto Memory_Error;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: /* add flex vectors ------------------------ */
|
||||
{
|
||||
T1_Int index;
|
||||
|
||||
if ( top[0] != 0 ) goto Unexpected_OtherSubr;
|
||||
|
||||
/* note that we should not add a point for index 0 */
|
||||
/* this will move our current position to the flex */
|
||||
/* point without adding any point to the outline */
|
||||
index = decoder->num_flex_vectors++;
|
||||
if (index > 0 && index < 7)
|
||||
add_point( builder,
|
||||
x,
|
||||
y,
|
||||
(T1_Byte)( index==3 || index==6 ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case 0: /* end flex feature ------------------------- */
|
||||
{
|
||||
if ( top[0] != 3 ) goto Unexpected_OtherSubr;
|
||||
|
||||
if ( decoder->flex_state == 0 ||
|
||||
decoder->num_flex_vectors != 7 )
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings: unexpected flex end\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
/* now consume the remaining "pop pop setcurpoint" */
|
||||
if ( ip+6 > limit ||
|
||||
ip[0] != 12 || ip[1] != 17 || /* pop */
|
||||
ip[2] != 12 || ip[3] != 17 || /* pop */
|
||||
ip[4] != 12 || ip[5] != 33 ) /* setcurpoint */
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings: invalid flex charstring\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
ip += 6;
|
||||
decoder->flex_state = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: /* change hints ---------------------------- */
|
||||
{
|
||||
if ( top[0] != 1 ) goto Unexpected_OtherSubr;
|
||||
|
||||
/* eat the following "pop" */
|
||||
if (ip+2 > limit)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings: invalid escape (12+%d)\n",
|
||||
ip[-1] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
if (ip[0] != 12 || ip[1] != 17)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings: 'pop' expected, found (%d %d)\n",
|
||||
ip[0], ip[1] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
ip += 2;
|
||||
break;;
|
||||
}
|
||||
|
||||
default:
|
||||
Unexpected_OtherSubr:
|
||||
FT_ERROR(( "T1.Parse_CharStrings: invalid othersubr [%d %d]!!\n",
|
||||
top[0], top[1] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
decoder->top = top;
|
||||
}
|
||||
else /* general operator */
|
||||
{
|
||||
T1_Int num_args = t1_args_count[op];
|
||||
|
||||
if ( top - decoder->stack < num_args )
|
||||
goto Stack_Underflow;
|
||||
|
||||
top -= num_args;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case op_endchar: /*************************************************/
|
||||
{
|
||||
FT_TRACE4(( " endchar" ));
|
||||
close_contour( builder );
|
||||
|
||||
/* add current outline to the glyph slot */
|
||||
builder->base.n_points += builder->current.n_points;
|
||||
builder->base.n_contours += builder->current.n_contours;
|
||||
|
||||
/* return now !! */
|
||||
FT_TRACE4(( "\n\n" ));
|
||||
return T1_Err_Ok;
|
||||
}
|
||||
|
||||
|
||||
case op_hsbw: /****************************************************/
|
||||
{
|
||||
FT_TRACE4(( " hsbw" ));
|
||||
builder->left_bearing.x += top[0];
|
||||
builder->advance.x = top[1];
|
||||
builder->advance.y = 0;
|
||||
|
||||
builder->last.x = x = top[0];
|
||||
builder->last.y = y = 0;
|
||||
|
||||
/* the "metrics_only" indicates that we only want to compute */
|
||||
/* the glyph's metrics (lsb + advance width), not load the */
|
||||
/* rest of it.. so exit immediately */
|
||||
if (builder->metrics_only)
|
||||
return T1_Err_Ok;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 4: /* vmoveto */
|
||||
|
||||
case op_seac: /****************************************************/
|
||||
/* return immediately after the processing */
|
||||
return t1operator_seac( decoder, top[0], top[1],
|
||||
top[2], top[3], top[4] );
|
||||
|
||||
case op_sbw: /****************************************************/
|
||||
{
|
||||
USE_ARGS(1);
|
||||
y += top[0];
|
||||
builder->path_begun = 1;
|
||||
goto Clear_Stack;
|
||||
FT_TRACE4(( " sbw" ));
|
||||
builder->left_bearing.x += top[0];
|
||||
builder->left_bearing.y += top[1];
|
||||
builder->advance.x = top[2];
|
||||
builder->advance.y = top[3];
|
||||
|
||||
builder->last.x = x = top[0];
|
||||
builder->last.y = y = top[1];
|
||||
|
||||
/* the "metrics_only" indicates that we only want to compute */
|
||||
/* the glyph's metrics (lsb + advance width), not load the */
|
||||
/* rest of it.. so exit immediately */
|
||||
if (builder->metrics_only)
|
||||
return T1_Err_Ok;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 5: /* rlineto */
|
||||
|
||||
|
||||
case op_closepath: /**********************************************/
|
||||
{
|
||||
FT_TRACE4(( " closepath" ));
|
||||
close_contour( builder );
|
||||
builder->path_begun = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case op_hlineto: /************************************************/
|
||||
{
|
||||
FT_TRACE4(( " hlineto" ));
|
||||
if ( start_point( builder, x, y ) ) goto Memory_Error;
|
||||
|
||||
x += top[0];
|
||||
goto Add_Line;
|
||||
}
|
||||
|
||||
|
||||
case op_hmoveto: /************************************************/
|
||||
{
|
||||
FT_TRACE4(( " hmoveto" ));
|
||||
x += top[0];
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case op_hvcurveto: /**********************************************/
|
||||
{
|
||||
FT_TRACE4(( " hvcurveto" ));
|
||||
if ( start_point( builder, x, y ) ||
|
||||
check_points( builder, 3 ) ) goto Memory_Error;
|
||||
|
||||
x += top[0];
|
||||
add_point( builder, x, y, 0 );
|
||||
x += top[1];
|
||||
y += top[2];
|
||||
add_point( builder, x, y, 0 );
|
||||
y += top[3];
|
||||
add_point( builder, x, y, 1 );
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case op_rlineto: /*************************************************/
|
||||
{
|
||||
FT_TRACE4(( " rlineto" ));
|
||||
if ( start_point( builder, x, y ) ) goto Memory_Error;
|
||||
|
||||
USE_ARGS(2);
|
||||
x += top[0];
|
||||
y += top[1];
|
||||
Add_Line:
|
||||
if (add_point1( builder, top[0], top[1] )) goto Memory_Error;
|
||||
goto Clear_Stack;
|
||||
if (add_point1( builder, x, y )) goto Memory_Error;
|
||||
break;
|
||||
}
|
||||
|
||||
case 6: /* hlineto */
|
||||
|
||||
|
||||
case op_rmoveto: /*************************************************/
|
||||
{
|
||||
if ( start_point( builder, x, y ) ) goto Memory_Error;
|
||||
|
||||
USE_ARGS(1);
|
||||
FT_TRACE4(( " rmoveto" ));
|
||||
x += top[0];
|
||||
goto Add_Line;
|
||||
y += top[1];
|
||||
break;
|
||||
}
|
||||
|
||||
case 7: /* vlineto */
|
||||
{
|
||||
if ( start_point( builder, x, y ) ) goto Memory_Error;
|
||||
|
||||
USE_ARGS(1);
|
||||
y += top[0];
|
||||
goto Add_Line;
|
||||
}
|
||||
|
||||
case 8: /* rrcurveto */
|
||||
|
||||
case op_rrcurveto: /***********************************************/
|
||||
{
|
||||
FT_TRACE4(( " rcurveto" ));
|
||||
if ( start_point( builder, x, y ) ||
|
||||
check_points( builder, 3 ) ) goto Memory_Error;
|
||||
|
||||
USE_ARGS(6);
|
||||
x += top[0];
|
||||
y += top[1];
|
||||
add_point( builder, x, y, 0 );
|
||||
|
@ -573,22 +952,64 @@
|
|||
x += top[4];
|
||||
y += top[5];
|
||||
add_point( builder, x, y, 1 );
|
||||
goto Clear_Stack;
|
||||
break;
|
||||
}
|
||||
|
||||
case 9: /* closepath */
|
||||
|
||||
|
||||
case op_vhcurveto: /**********************************************/
|
||||
{
|
||||
close_contour( builder );
|
||||
builder->path_begun = 0;
|
||||
FT_TRACE4(( " vhcurveto" ));
|
||||
if ( start_point( builder, x, y ) ||
|
||||
check_points( builder, 3 ) ) goto Memory_Error;
|
||||
|
||||
y += top[0];
|
||||
add_point( builder, x, y, 0 );
|
||||
x += top[1];
|
||||
y += top[2];
|
||||
add_point( builder, x, y, 0 );
|
||||
x += top[3];
|
||||
add_point( builder, x, y, 1 );
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 10: /* callsubr */
|
||||
|
||||
|
||||
case op_vlineto: /************************************************/
|
||||
{
|
||||
FT_TRACE4(( " vlineto" ));
|
||||
if ( start_point( builder, x, y ) ) goto Memory_Error;
|
||||
|
||||
y += top[0];
|
||||
goto Add_Line;
|
||||
}
|
||||
|
||||
|
||||
case op_vmoveto: /************************************************/
|
||||
{
|
||||
FT_TRACE4(( " vmoveto" ));
|
||||
y += top[0];
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case op_div: /****************************************************/
|
||||
{
|
||||
FT_TRACE4(( " div" ));
|
||||
if (top[1])
|
||||
*top++ = top[0] / top[1];
|
||||
else
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : division by 0\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case op_callsubr: /***********************************************/
|
||||
{
|
||||
T1_Int index;
|
||||
|
||||
USE_ARGS(1);
|
||||
|
||||
FT_TRACE4(( " callsubr" ));
|
||||
index = top[0];
|
||||
if ( index < 0 || index >= num_subrs )
|
||||
{
|
||||
|
@ -618,13 +1039,21 @@
|
|||
decoder->zone = zone;
|
||||
ip = zone->base;
|
||||
limit = zone->limit;
|
||||
|
||||
/* do not clear stack */
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 11: /* return */
|
||||
|
||||
|
||||
case op_pop: /****************************************************/
|
||||
{
|
||||
FT_TRACE4(( " pop" ));
|
||||
FT_ERROR(( "T1.Parse_CharStrings : unexpected POP\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
|
||||
case op_return: /************************************************/
|
||||
{
|
||||
FT_TRACE4(( " return" ));
|
||||
if ( zone <= decoder->zones )
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : unexpected return\n" ));
|
||||
|
@ -635,306 +1064,58 @@
|
|||
ip = zone->cursor;
|
||||
limit = zone->limit;
|
||||
decoder->zone = zone;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 13: /* hsbw */
|
||||
|
||||
case op_dotsection: /*********************************************/
|
||||
{
|
||||
USE_ARGS(2);
|
||||
builder->left_bearing.x += top[0];
|
||||
builder->advance.x = top[1];
|
||||
builder->advance.y = 0;
|
||||
|
||||
builder->last.x = x = top[0];
|
||||
builder->last.y = y = 0;
|
||||
|
||||
/* the "metrics_only" indicates that we only want to compute */
|
||||
/* the glyph's metrics (lsb + advance width), not load the */
|
||||
/* rest of it.. so exit immediately */
|
||||
if (builder->metrics_only)
|
||||
return T1_Err_Ok;
|
||||
|
||||
goto Clear_Stack;
|
||||
FT_TRACE4(( " dotsection" ));
|
||||
break;
|
||||
}
|
||||
|
||||
case 14: /* endchar */
|
||||
case op_hstem: /**************************************************/
|
||||
{
|
||||
close_contour( builder );
|
||||
|
||||
/* add current outline to the glyph slot */
|
||||
builder->base.n_points += builder->current.n_points;
|
||||
builder->base.n_contours += builder->current.n_contours;
|
||||
|
||||
/* return now !! */
|
||||
return T1_Err_Ok;
|
||||
FT_TRACE4(( " hstem" ));
|
||||
break;
|
||||
}
|
||||
|
||||
case 21: /* rmoveto */
|
||||
|
||||
case op_hstem3: /*************************************************/
|
||||
{
|
||||
USE_ARGS(2);
|
||||
x += top[0];
|
||||
y += top[1];
|
||||
goto Clear_Stack;
|
||||
FT_TRACE4(( " hstem3" ));
|
||||
break;
|
||||
}
|
||||
|
||||
case 22: /* hmoveto */
|
||||
|
||||
case op_vstem: /**************************************************/
|
||||
{
|
||||
USE_ARGS(1);
|
||||
x += top[0];
|
||||
goto Clear_Stack;
|
||||
FT_TRACE4(( " vstem" ));
|
||||
break;
|
||||
}
|
||||
|
||||
case 30: /* vhcurveto */
|
||||
|
||||
case op_vstem3: /*************************************************/
|
||||
{
|
||||
if ( start_point( builder, x, y ) ||
|
||||
check_points( builder, 3 ) ) goto Memory_Error;
|
||||
|
||||
USE_ARGS(4);
|
||||
y += top[0];
|
||||
add_point( builder, x, y, 0 );
|
||||
x += top[1];
|
||||
y += top[2];
|
||||
add_point( builder, x, y, 0 );
|
||||
x += top[3];
|
||||
add_point( builder, x, y, 1 );
|
||||
goto Clear_Stack;
|
||||
FT_TRACE4(( " vstem3" ));
|
||||
break;
|
||||
}
|
||||
|
||||
case 31: /* hvcurveto */
|
||||
|
||||
case op_setcurrentpoint: /*****************************************/
|
||||
{
|
||||
if ( start_point( builder, x, y ) ||
|
||||
check_points( builder, 3 ) ) goto Memory_Error;
|
||||
|
||||
USE_ARGS(4);
|
||||
x += top[0];
|
||||
add_point( builder, x, y, 0 );
|
||||
x += top[1];
|
||||
y += top[2];
|
||||
add_point( builder, x, y, 0 );
|
||||
y += top[3];
|
||||
add_point( builder, x, y, 1 );
|
||||
goto Clear_Stack;
|
||||
FT_TRACE4(( " setcurrentpoint" ));
|
||||
FT_ERROR(( "T1.Parse_CharStrings : unexpected SETCURRENTPOINT\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
|
||||
case 12:
|
||||
{
|
||||
if (ip > limit)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : invalid escape (12+EOF)\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
default:
|
||||
FT_ERROR(( "T1.Parse_CharStrings : unhandled opcode %d\n", op ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
switch (*ip++)
|
||||
{
|
||||
case 0: /* dotsection */
|
||||
case 1: /* vstem3 */
|
||||
case 2: /* hstem3 */
|
||||
goto Clear_Stack;
|
||||
|
||||
case 6: /* seac */
|
||||
{
|
||||
USE_ARGS(5);
|
||||
|
||||
/* return immediately to implement an accented character */
|
||||
return t1operator_seac( decoder,
|
||||
top[0], top[1], top[3],
|
||||
top[4], top[5] );
|
||||
}
|
||||
|
||||
case 7: /* sbw */
|
||||
{
|
||||
USE_ARGS(4);
|
||||
builder->left_bearing.x += top[0];
|
||||
builder->left_bearing.y += top[1];
|
||||
builder->advance.x = top[2];
|
||||
builder->advance.y = top[3];
|
||||
|
||||
builder->last.x = x = top[0];
|
||||
builder->last.y = y = top[1];
|
||||
|
||||
/* the "metrics_only" indicates that we only want to compute */
|
||||
/* the glyph's metrics (lsb + advance width), not load the */
|
||||
/* rest of it.. so exit immediately */
|
||||
if (builder->metrics_only)
|
||||
return T1_Err_Ok;
|
||||
|
||||
goto Clear_Stack;
|
||||
}
|
||||
|
||||
case 12: /* div */
|
||||
{
|
||||
USE_ARGS(2);
|
||||
top[0] /= top[1];
|
||||
top++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 16: /* callothersubr */
|
||||
{
|
||||
USE_ARGS(1);
|
||||
switch (top[0])
|
||||
{
|
||||
case 1: /* start flex feature ---------------------- */
|
||||
{
|
||||
decoder->flex_state = 1;
|
||||
decoder->num_flex_vectors = 0;
|
||||
if ( start_point(builder, x, y) ||
|
||||
check_points(builder,6) ) goto Memory_Error;
|
||||
}
|
||||
break;
|
||||
decoder->top = top;
|
||||
|
||||
} /* general operator processing */
|
||||
|
||||
case 2: /* add flex vectors ------------------------ */
|
||||
{
|
||||
T1_Int index;
|
||||
|
||||
/* note that we should not add a point for index 0 */
|
||||
/* this will move our current position to the flex */
|
||||
/* point without adding any point to the outline */
|
||||
index = decoder->num_flex_vectors++;
|
||||
if (index > 0 && index < 7)
|
||||
add_point( builder,
|
||||
x,
|
||||
y,
|
||||
(T1_Byte)( index==3 || index==6 ) );
|
||||
}
|
||||
break;
|
||||
|
||||
case 0: /* end flex feature ------------------------- */
|
||||
{
|
||||
USE_ARGS(3); /* ignore parameters */
|
||||
|
||||
if ( decoder->flex_state == 0 ||
|
||||
decoder->num_flex_vectors != 7 )
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings: unexpected flex end\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
/* now consume the remaining "pop pop setcurpoint" */
|
||||
if ( ip+6 > limit ||
|
||||
ip[0] != 12 || ip[1] != 17 || /* pop */
|
||||
ip[2] != 12 || ip[3] != 17 || /* pop */
|
||||
ip[4] != 12 || ip[5] != 33 ) /* setcurpoint */
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings: invalid flex charstring\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
ip += 6;
|
||||
decoder->flex_state = 0;
|
||||
decoder->top = top;
|
||||
|
||||
goto Clear_Stack;
|
||||
}
|
||||
|
||||
case 3: /* change hints ---------------------------- */
|
||||
{
|
||||
/* eat the following "pop" */
|
||||
if (ip+2 > limit)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings: invalid escape (12+%d)\n",
|
||||
ip[-1] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
if (ip[0] != 12 || ip[1] != 17)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings: 'pop' expected, found (%d %d)\n",
|
||||
ip[0], ip[1] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
ip += 2;
|
||||
goto Clear_Stack;
|
||||
}
|
||||
|
||||
default:
|
||||
FT_ERROR(( "T1.Parse_CharStrings: invalid othersubr %d !!\n",
|
||||
top[0] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
}
|
||||
|
||||
case 17: /* pop - should not happen !! */
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : 'pop' should not happen !!\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
case 33: /* setcurrentpoint */
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : 'setcurrentpoint' should not happen !!\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
default:
|
||||
FT_ERROR(( "T1.Parse_CharStrings : invalid escape (12+%d)\n",
|
||||
ip[-1] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
}
|
||||
break; /* escape - 12 */
|
||||
|
||||
case 255: /* four bytes integer */
|
||||
{
|
||||
if (ip+4 > limit)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : unexpected EOF in integer\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
*top++ = ((long)ip[0] << 24) |
|
||||
((long)ip[1] << 16) |
|
||||
((long)ip[2] << 8) |
|
||||
ip[3];
|
||||
ip += 4;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
T1_Long v, v2;
|
||||
|
||||
v = ip[-1];
|
||||
if (v < 32)
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : invalid byte (%d)\n",
|
||||
ip[-1] ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
/* compute value ---- */
|
||||
/* */
|
||||
if (v < 247) /* 1-byte value */
|
||||
v -= 139;
|
||||
else
|
||||
{
|
||||
if (++ip > limit) /* 2-bytes value, check limits */
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : unexpected EOF in integer\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
v2 = ip[-1] + 108;
|
||||
if (v < 251)
|
||||
v = ((v-247) << 8) + v2;
|
||||
else
|
||||
v = -(((v-251) << 8) + v2);
|
||||
}
|
||||
|
||||
/* store value - is there enough room ?*/
|
||||
if ( top >= decoder->stack + T1_MAX_CHARSTRINGS_OPERANDS )
|
||||
{
|
||||
FT_ERROR(( "T1.Parse_CharStrings : Stack overflow !!\n" ));
|
||||
goto Syntax_Error;
|
||||
}
|
||||
|
||||
*top++ = v;
|
||||
decoder->top = top;
|
||||
}
|
||||
} /* big switch */
|
||||
|
||||
} /* while ip < limit */
|
||||
FT_TRACE4(( "..end..\n\n" ));
|
||||
return error;
|
||||
|
||||
Syntax_Error:
|
||||
|
|
|
@ -63,10 +63,8 @@
|
|||
|
||||
#include <t1types.h>
|
||||
#include <t1errors.h>
|
||||
#include <t1encode.h>
|
||||
#include <t1config.h>
|
||||
#include <t1load.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#undef FT_COMPONENT
|
||||
|
@ -345,8 +343,12 @@
|
|||
{
|
||||
if ( cur[1] == 'e' &&
|
||||
cur[2] == 'f' &&
|
||||
is_space(cur[-1]) &&
|
||||
is_space(cur[3]) )
|
||||
break;
|
||||
{
|
||||
FT_TRACE6(( "encoding end\n" ));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* otherwise, we must find a number before anything else */
|
||||
|
@ -382,6 +384,7 @@
|
|||
}
|
||||
|
||||
face->type1.encoding_type = t1_encoding_array;
|
||||
parser->cursor = cur;
|
||||
}
|
||||
/* Otherwise, we should have either "StandardEncoding" or */
|
||||
/* "ExpertEncoding" */
|
||||
|
@ -433,6 +436,10 @@
|
|||
index = T1_ToInt(parser);
|
||||
if (!read_binary_data(parser,&size,&base)) return;
|
||||
|
||||
T1_Decrypt( base, size, 4330 );
|
||||
size -= face->type1.lenIV;
|
||||
base += face->type1.lenIV;
|
||||
|
||||
error = T1_Add_Table( table, index, base, size );
|
||||
if (error) goto Fail;
|
||||
}
|
||||
|
@ -481,13 +488,19 @@
|
|||
cur = parser->cursor;
|
||||
if (cur >= limit) break;
|
||||
|
||||
/* we stop when we find a "def" */
|
||||
/* we stop when we find a "def" or "end" keyword */
|
||||
if (*cur == 'd' &&
|
||||
cur+3 < limit &&
|
||||
cur[1] == 'e' &&
|
||||
cur[2] == 'f' )
|
||||
break;
|
||||
|
||||
if (*cur == 'e' &&
|
||||
cur+3 < limit &&
|
||||
cur[1] == 'n' &&
|
||||
cur[2] == 'd' )
|
||||
break;
|
||||
|
||||
if (*cur != '/')
|
||||
skip_blackspace(parser);
|
||||
else
|
||||
|
@ -497,6 +510,7 @@
|
|||
|
||||
while (cur2 < limit && is_alpha(*cur2)) cur2++;
|
||||
len = cur2-cur-1;
|
||||
|
||||
error = T1_Add_Table( name_table, n, cur+1, len+1 );
|
||||
if (error) goto Fail;
|
||||
|
||||
|
@ -505,7 +519,11 @@
|
|||
|
||||
parser->cursor = cur2;
|
||||
if (!read_binary_data(parser,&size,&base)) return;
|
||||
|
||||
|
||||
T1_Decrypt( base, size, 4330 );
|
||||
size -= face->type1.lenIV;
|
||||
base += face->type1.lenIV;
|
||||
|
||||
error = T1_Add_Table( code_table, n, base, size );
|
||||
if (error) goto Fail;
|
||||
|
||||
|
@ -514,6 +532,7 @@
|
|||
break;
|
||||
}
|
||||
}
|
||||
loader->num_glyphs = n;
|
||||
return;
|
||||
|
||||
Fail:
|
||||
|
@ -562,7 +581,7 @@
|
|||
T1_Error parse_dict( T1_Face face,
|
||||
T1_Loader* loader,
|
||||
T1_Byte* base,
|
||||
T1_Int size )
|
||||
T1_Long size )
|
||||
{
|
||||
T1_Parser* parser = &loader->parser;
|
||||
|
||||
|
@ -571,8 +590,8 @@
|
|||
parser->error = 0;
|
||||
|
||||
{
|
||||
T1_Byte* cur = base;
|
||||
T1_Byte* limit = cur + size;
|
||||
T1_Byte* cur = base;
|
||||
T1_Byte* limit = cur + size;
|
||||
|
||||
for ( ;cur < limit; cur++ )
|
||||
{
|
||||
|
@ -587,7 +606,7 @@
|
|||
while (cur2 < limit && is_alpha(*cur2)) cur2++;
|
||||
len = cur2-cur;
|
||||
|
||||
if (len > 0)
|
||||
if (len > 0 && len < 20)
|
||||
{
|
||||
/* now, compare the immediate name to the keyword table */
|
||||
T1_KeyWord* keyword = (T1_KeyWord*)t1_keywords;
|
||||
|
@ -617,6 +636,7 @@
|
|||
return parser->error;
|
||||
|
||||
cur = parser->cursor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
keyword++;
|
||||
|
@ -631,9 +651,11 @@
|
|||
static
|
||||
void t1_init_loader( T1_Loader* loader, T1_Face face )
|
||||
{
|
||||
MEM_Set( loader, 0, sizeof(*loader) );
|
||||
loader->num_glyphs = 0;
|
||||
loader->num_chars = 0;
|
||||
|
||||
|
||||
/* initialize the tables - simply set their 'init' field to 0 */
|
||||
loader->encoding_table.init = 0;
|
||||
loader->charstrings.init = 0;
|
||||
loader->glyph_names.init = 0;
|
||||
|
@ -685,6 +707,18 @@
|
|||
/* to the Type1 data */
|
||||
type1->num_glyphs = loader.num_glyphs;
|
||||
|
||||
if ( !loader.subrs.init )
|
||||
{
|
||||
FT_ERROR(( "T1.Open_Face: no subrs array in face !!\n" ));
|
||||
error = FT_Err_Invalid_File_Format;
|
||||
}
|
||||
|
||||
if ( !loader.charstrings.init )
|
||||
{
|
||||
FT_ERROR(( "T1.Open_Face: no charstrings array in face !!\n" ));
|
||||
error = FT_Err_Invalid_File_Format;
|
||||
}
|
||||
|
||||
loader.subrs.init = 0;
|
||||
type1->num_subrs = loader.num_subrs;
|
||||
type1->subrs_block = loader.subrs.block;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include <t1gload.h>
|
||||
#include <t1load.h>
|
||||
#include <psnames.h>
|
||||
|
||||
/* Required by tracing mode */
|
||||
#undef FT_COMPONENT
|
||||
|
@ -164,12 +165,25 @@
|
|||
T1_Face face )
|
||||
{
|
||||
T1_Error error;
|
||||
PSNames_Interface* psnames;
|
||||
|
||||
(void)face_index;
|
||||
(void)face;
|
||||
|
||||
face->root.num_faces = 1;
|
||||
|
||||
psnames = (PSNames_Interface*)face->psnames;
|
||||
if (!psnames)
|
||||
{
|
||||
/* look-up the PSNames driver */
|
||||
FT_Driver psnames_driver;
|
||||
|
||||
psnames_driver = FT_Get_Driver( face->root.driver->library, "psnames" );
|
||||
if (psnames_driver)
|
||||
face->psnames = (PSNames_Interface*)
|
||||
(psnames_driver->interface.format_interface);
|
||||
}
|
||||
|
||||
/* open the tokenizer, this will also check the font format */
|
||||
error = T1_Open_Face( face );
|
||||
if (error) goto Exit;
|
||||
|
|
|
@ -367,9 +367,18 @@
|
|||
cur++;
|
||||
|
||||
/* now, read the coordinates */
|
||||
for ( ; cur < limit; cur++ )
|
||||
for ( ; cur < limit; )
|
||||
{
|
||||
c = *cur;
|
||||
/* skip whitespace in front of data */
|
||||
for (;;)
|
||||
{
|
||||
c = *cur;
|
||||
if ( c != ' ' && c != '\t' ) break;
|
||||
|
||||
cur++;
|
||||
if (cur >= limit) goto Exit;
|
||||
}
|
||||
|
||||
if (count >= max_coords || c == ender)
|
||||
break;
|
||||
|
||||
|
@ -414,9 +423,18 @@
|
|||
cur++;
|
||||
|
||||
/* now, read the values */
|
||||
for ( ; cur < limit; cur++ )
|
||||
for ( ; cur < limit; )
|
||||
{
|
||||
c = *cur;
|
||||
/* skip whitespace in front of data */
|
||||
for (;;)
|
||||
{
|
||||
c = *cur;
|
||||
if ( c != ' ' && c != '\t' ) break;
|
||||
|
||||
cur++;
|
||||
if (cur >= limit) goto Exit;
|
||||
}
|
||||
|
||||
if (count >= max_values || c == ender)
|
||||
break;
|
||||
|
||||
|
@ -442,10 +460,19 @@
|
|||
T1_String* result;
|
||||
FT_Error error;
|
||||
|
||||
/* first of all, skip everything until we encounter a string */
|
||||
while ( cur < limit && *cur != '(' ) cur++;
|
||||
cur++;
|
||||
if (cur >= limit) return 0;
|
||||
/* XXX : some stupid fonts have a "Notice" or "Copyright" string */
|
||||
/* that simply doesn't begin with an opening parenthesis, even */
|
||||
/* though they have a closing one !!! E.g. "amuncial.pfb" */
|
||||
/* */
|
||||
/* We must deal with these ill-fated cases there. Note that */
|
||||
/* these fonts didn't work with the old Type 1 driver as the */
|
||||
/* notice/copyright was not recognized as a valid string token */
|
||||
/* and made the old token parser commit errors.. */
|
||||
|
||||
while ( cur < limit && (*cur == ' ' || *cur == '\t')) cur++;
|
||||
if (cur+1 >= limit) return 0;
|
||||
|
||||
if (*cur == '(') cur++; /* skip the opening parenthesis, if there is one */
|
||||
|
||||
*cursor = cur;
|
||||
count = 0;
|
||||
|
@ -470,7 +497,7 @@
|
|||
/* now copy the string */
|
||||
MEM_Copy( result, *cursor, len );
|
||||
result[len] = '\0';
|
||||
|
||||
*cursor = cur;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -632,43 +659,44 @@
|
|||
else
|
||||
parser->in_pfb = 1;
|
||||
|
||||
/* now, try to load the "size" bytes of the "base" dictionary we */
|
||||
/* found previously */
|
||||
|
||||
/* if it's a memory-based resource, set up pointers */
|
||||
if ( !stream->read )
|
||||
{
|
||||
parser->base_dict = (T1_Byte*)stream->base + stream->pos;
|
||||
parser->base_len = size;
|
||||
parser->in_memory = 1;
|
||||
|
||||
/* check that the "size" field is valid */
|
||||
if ( FILE_Skip(size) ) goto Exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* read segment in memory */
|
||||
if ( ALLOC( parser->base_dict, size ) ||
|
||||
FILE_Read( parser->base_dict, size ) )
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
/* Now check font format, we must see a '%!PS-AdobeFont-1' */
|
||||
/* or a '%!FontType' */
|
||||
{
|
||||
if ( size <= 16 ||
|
||||
( strncmp( (const char*)parser->base_dict, "%!PS-AdobeFont-1", 16 ) &&
|
||||
strncmp( (const char*)parser->base_dict, "%!FontType", 10 ) ) )
|
||||
{
|
||||
FT_TRACE2(( "Not a Type1 font\n" ));
|
||||
error = T1_Err_Invalid_File_Format;
|
||||
}
|
||||
else
|
||||
{
|
||||
parser->cursor = parser->base_dict;
|
||||
parser->limit = parser->cursor + parser->base_len;
|
||||
}
|
||||
}
|
||||
/* now, try to load the "size" bytes of the "base" dictionary we */
|
||||
/* found previously */
|
||||
|
||||
/* if it's a memory-based resource, set up pointers */
|
||||
if ( !stream->read )
|
||||
{
|
||||
parser->base_dict = (T1_Byte*)stream->base + stream->pos;
|
||||
parser->base_len = size;
|
||||
parser->in_memory = 1;
|
||||
|
||||
/* check that the "size" field is valid */
|
||||
if ( FILE_Skip(size) ) goto Exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* read segment in memory */
|
||||
if ( ALLOC( parser->base_dict, size ) ||
|
||||
FILE_Read( parser->base_dict, size ) )
|
||||
goto Exit;
|
||||
parser->base_len = size;
|
||||
}
|
||||
|
||||
/* Now check font format, we must see a '%!PS-AdobeFont-1' */
|
||||
/* or a '%!FontType' */
|
||||
{
|
||||
if ( size <= 16 ||
|
||||
( strncmp( (const char*)parser->base_dict, "%!PS-AdobeFont-1", 16 ) &&
|
||||
strncmp( (const char*)parser->base_dict, "%!FontType", 10 ) ) )
|
||||
{
|
||||
FT_TRACE2(( "Not a Type1 font\n" ));
|
||||
error = T1_Err_Invalid_File_Format;
|
||||
}
|
||||
else
|
||||
{
|
||||
parser->cursor = parser->base_dict;
|
||||
parser->limit = parser->cursor + parser->base_len;
|
||||
}
|
||||
}
|
||||
|
||||
Exit:
|
||||
if (error && !parser->in_memory)
|
||||
|
|
|
@ -34,5 +34,8 @@
|
|||
#include <t1objs.c>
|
||||
#include <t1driver.c>
|
||||
#include <t1gload.c>
|
||||
#include <t1encode.c>
|
||||
|
||||
#ifndef T1_CONFIG_OPTION_NO_AFM
|
||||
#include <t1afm.c>
|
||||
#endif
|
||||
|
||||
|
|
Loading…
Reference in New Issue