static SplineFont *SFD_GetFont( FILE *sfd,SplineFont *cidmaster,char *tok,
				int fromdir, char *dirname, float sfdversion )
{
    SplineFont *sf;
    int realcnt, i, eof, mappos=-1, ch;
    struct table_ordering *lastord = NULL;
    struct axismap *lastaxismap = NULL;
    struct named_instance *lastnamedinstance = NULL;
    int pushedbacktok = false;
    Encoding *enc = &custom;
    struct remap *remap = NULL;
    int haddupenc;
    int old_style_order2 = false;
    int had_layer_cnt=false;

    orig_pos = 0;		/* Only used for compatibility with extremely old sfd files */

    sf = SplineFontEmpty();
    if ( sfdversion>0 && sfdversion<2 ) {
	/* If it's an old style sfd file with old style features we need some */
	/*  extra data space to do the conversion from old to new */
	sf = realloc(sf,sizeof(SplineFont1));
	memset(((uint8 *) sf) + sizeof(SplineFont),0,sizeof(SplineFont1)-sizeof(SplineFont));
    }
    sf->sfd_version = sfdversion;
    sf->cidmaster = cidmaster;
    sf->uni_interp = ui_unset;
	SFD_GetFontMetaDataData d;
	SFD_GetFontMetaDataData_Init( &d );
    while ( 1 ) {
	if ( pushedbacktok )
	    pushedbacktok = false;
	else if ( (eof = getname(sfd,tok))!=1 ) {
	    if ( eof==-1 )
    break;
	    geteol(sfd,tok);
    continue;
	}

	bool wasMetadata = SFD_GetFontMetaData( sfd, tok, sf, &d );
	had_layer_cnt = d.had_layer_cnt;
        if( wasMetadata )
        {
            // we have handled the token entirely
            // inside SFD_GetFontMetaData() move to next token.
            continue;
        }

	if ( strmatch(tok,"DisplaySize:")==0 )
	{
	    getint(sfd,&sf->display_size);
	}
	else if ( strmatch(tok,"DisplayLayer:")==0 )
	{
	    getint(sfd,&sf->display_layer);
	}
	else if ( strmatch(tok,"ExtremaBound:")==0 )
	{
	    getint(sfd,&sf->extrema_bound);
	}
	else if ( strmatch(tok,"WidthSeparation:")==0 )
	{
	    getint(sfd,&sf->width_separation);
	}
	else if ( strmatch(tok,"WinInfo:")==0 )
	{
	    int temp1, temp2;
	    getint(sfd,&sf->top_enc);
	    getint(sfd,&temp1);
	    getint(sfd,&temp2);
	    if ( sf->top_enc<=0 ) sf->top_enc=-1;
	    if ( temp1<=0 ) temp1 = 16;
	    if ( temp2<=0 ) temp2 = 4;
	    sf->desired_col_cnt = temp1;
	    sf->desired_row_cnt = temp2;
	}
	else if ( strmatch(tok,"AntiAlias:")==0 )
	{
	    int temp;
	    getint(sfd,&temp);
	    sf->display_antialias = temp;
	}
	else if ( strmatch(tok,"FitToEm:")==0 )
	{
	    int temp;
	    getint(sfd,&temp);
	    sf->display_bbsized = temp;
	}
	else if ( strmatch(tok,"OnlyBitmaps:")==0 )
	{
	    int temp;
	    getint(sfd,&temp);
	    sf->onlybitmaps = temp;
	}
	else if ( strmatch(tok,"Order2:")==0 )
	{
	    getint(sfd,&old_style_order2);
	    sf->grid.order2 = old_style_order2;
	    sf->layers[ly_back].order2 = old_style_order2;
	    sf->layers[ly_fore].order2 = old_style_order2;
	}
	else if ( strmatch(tok,"GridOrder2:")==0 )
	{
	    int o2;
	    getint(sfd,&o2);
	    sf->grid.order2 = o2;
	}
	else if ( strmatch(tok,"Encoding:")==0 )
	{
	    enc = SFDGetEncoding(sfd,tok);
	    if ( sf->map!=NULL ) sf->map->enc = enc;
	}
	else if ( strmatch(tok,"OldEncoding:")==0 )
	{
	    /* old_encname =*/ (void) SFDGetEncoding(sfd,tok);
	}
	else if ( strmatch(tok,"UnicodeInterp:")==0 )
	{
	    sf->uni_interp = SFDGetUniInterp(sfd,tok,sf);
	}
	else if ( strmatch(tok,"NameList:")==0 )
	{
	    SFDGetNameList(sfd,tok,sf);
	}
	else if ( strmatch(tok,"Compacted:")==0 )
	{
	    int temp;
	    getint(sfd,&temp);
	    sf->compacted = temp;
	}
	else if ( strmatch(tok,"Registry:")==0 )
	{
	    geteol(sfd,tok);
	    sf->cidregistry = copy(tok);
	}

	//////////

	else if ( strmatch(tok,"Ordering:")==0 ) {
	    geteol(sfd,tok);
	    sf->ordering = copy(tok);
	} else if ( strmatch(tok,"Supplement:")==0 ) {
	    getint(sfd,&sf->supplement);
	} else if ( strmatch(tok,"RemapN:")==0 ) {
	    int n;
	    getint(sfd,&n);
	    remap = calloc(n+1,sizeof(struct remap));
	    remap[n].infont = -1;
	    mappos = 0;
	    if ( sf->map!=NULL ) sf->map->remap = remap;
	} else if ( strmatch(tok,"Remap:")==0 ) {
	    uint32 f, l; int p;
	    gethex(sfd,&f);
	    gethex(sfd,&l);
	    getint(sfd,&p);
	    if ( remap!=NULL && remap[mappos].infont!=-1 ) {
		remap[mappos].firstenc = f;
		remap[mappos].lastenc = l;
		remap[mappos].infont = p;
		mappos++;
	    }
	} else if ( strmatch(tok,"CIDVersion:")==0 ) {
	    real temp;
	    getreal(sfd,&temp);
	    sf->cidversion = temp;
	} else if ( strmatch(tok,"Grid")==0 ) {
	    sf->grid.splines = SFDGetSplineSet(sfd,sf->grid.order2);
	} else if ( strmatch(tok,"ScriptLang:")==0 ) {
	    int i,j,k;
	    int imax, jmax, kmax;
	    if ( sf->sfd_version==0 || sf->sfd_version>=2 ) {
		IError( "Script lang lists should not happen in version 2 sfd files." );
                SplineFontFree(sf);
                return NULL;
	    }
	    getint(sfd,&imax);
	    ((SplineFont1 *) sf)->sli_cnt = imax;
	    ((SplineFont1 *) sf)->script_lang = malloc((imax+1)*sizeof(struct script_record *));
	    ((SplineFont1 *) sf)->script_lang[imax] = NULL;
	    for ( i=0; i<imax; ++i ) {
		getint(sfd,&jmax);
		((SplineFont1 *) sf)->script_lang[i] = malloc((jmax+1)*sizeof(struct script_record));
		((SplineFont1 *) sf)->script_lang[i][jmax].script = 0;
		for ( j=0; j<jmax; ++j ) {
		    ((SplineFont1 *) sf)->script_lang[i][j].script = gettag(sfd);
		    getint(sfd,&kmax);
		    ((SplineFont1 *) sf)->script_lang[i][j].langs = malloc((kmax+1)*sizeof(uint32));
		    ((SplineFont1 *) sf)->script_lang[i][j].langs[kmax] = 0;
		    for ( k=0; k<kmax; ++k ) {
			((SplineFont1 *) sf)->script_lang[i][j].langs[k] = gettag(sfd);
		    }
		}
	    }
	} else if ( strmatch(tok,"TeXData:")==0 ) {
	    int temp;
	    getint(sfd,&temp);
	    sf->texdata.type = temp;
	    getint(sfd, &temp);
	    if ( sf->design_size==0 ) {
	    	sf->design_size = (5*temp+(1<<18))>>19;
	    }
	    for ( i=0; i<22; ++i ) {
		int foo;
		getint(sfd,&foo);
		sf->texdata.params[i]=foo;
	    }
	} else if ( strnmatch(tok,"AnchorClass",11)==0 ) {
	    char *name;
	    AnchorClass *lastan = NULL, *an;
	    int old = strchr(tok,'2')==NULL;
	    while ( (name=SFDReadUTF7Str(sfd))!=NULL ) {
		an = chunkalloc(old ? sizeof(AnchorClass1) : sizeof(AnchorClass));
		an->name = name;
		if ( old ) {
		    getname(sfd,tok);
		    if ( tok[0]=='0' && tok[1]=='\0' )
			((AnchorClass1 *) an)->feature_tag = 0;
		    else {
			if ( tok[1]=='\0' ) { tok[1]=' '; tok[2] = 0; }
			if ( tok[2]=='\0' ) { tok[2]=' '; tok[3] = 0; }
			if ( tok[3]=='\0' ) { tok[3]=' '; tok[4] = 0; }
			((AnchorClass1 *) an)->feature_tag = (tok[0]<<24) | (tok[1]<<16) | (tok[2]<<8) | tok[3];
		    }
		    while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
		    ungetc(ch,sfd);
		    if ( isdigit(ch)) {
			int temp;
			getint(sfd,&temp);
			((AnchorClass1 *) an)->flags = temp;
		    }
		    while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
		    ungetc(ch,sfd);
		    if ( isdigit(ch)) {
			int temp;
			getint(sfd,&temp);
			((AnchorClass1 *) an)->script_lang_index = temp;
		    } else
			((AnchorClass1 *) an)->script_lang_index = 0xffff;		/* Will be fixed up later */
		    while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
		    ungetc(ch,sfd);
		    if ( isdigit(ch)) {
			int temp;
			getint(sfd,&temp);
			((AnchorClass1 *) an)->merge_with = temp;
		    } else
			((AnchorClass1 *) an)->merge_with = 0xffff;			/* Will be fixed up later */
		} else {
                    char *subtable_name = SFDReadUTF7Str(sfd);
                    if ( subtable_name!=NULL)                                           /* subtable is optional */
		        an->subtable = SFFindLookupSubtableAndFreeName(sf,subtable_name);
                }
		while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
		ungetc(ch,sfd);
		if ( isdigit(ch) ) {
		    /* Early versions of SfdFormat 2 had a number here */
		    int temp;
		    getint(sfd,&temp);
		    an->type = temp;
		} else if ( old ) {
		    if ( ((AnchorClass1 *) an)->feature_tag==CHR('c','u','r','s'))
			an->type = act_curs;
		    else if ( ((AnchorClass1 *) an)->feature_tag==CHR('m','k','m','k'))
			an->type = act_mkmk;
		    else
			an->type = act_mark;
		} else {
		    an->type = act_mark;
		    if( an->subtable && an->subtable->lookup )
		    {
			switch ( an->subtable->lookup->lookup_type )
			{
			case gpos_cursive:
			    an->type = act_curs;
			    break;
			case gpos_mark2base:
			    an->type = act_mark;
			    break;
			case gpos_mark2ligature:
			    an->type = act_mklg;
			    break;
			case gpos_mark2mark:
			    an->type = act_mkmk;
			    break;
			default:
			    an->type = act_mark;
			    break;
			}
		    }
		}
		if ( lastan==NULL )
		    sf->anchor = an;
		else
		    lastan->next = an;
		lastan = an;
	    }
	} else if ( strncmp(tok,"MATH:",5)==0 ) {
	    SFDParseMathItem(sfd,sf,tok);
	} else if ( strmatch(tok,"TableOrder:")==0 ) {
	    int temp;
	    struct table_ordering *ord;
	    if ( sfdversion==0 || sfdversion>=2 ) {
		IError("Table ordering specified in version 2 sfd file.\n" );
                SplineFontFree(sf);
                return NULL;
	    }
	    ord = chunkalloc(sizeof(struct table_ordering));
	    ord->table_tag = gettag(sfd);
	    getint(sfd,&temp);
	    ord->ordered_features = malloc((temp+1)*sizeof(uint32));
	    ord->ordered_features[temp] = 0;
	    for ( i=0; i<temp; ++i ) {
		while ( isspace((ch=nlgetc(sfd))) );
		if ( ch=='\'' ) {
		    ungetc(ch,sfd);
		    ord->ordered_features[i] = gettag(sfd);
		} else if ( ch=='<' ) {
		    int f,s;
		    fscanf(sfd,"%d,%d>", &f, &s );
		    ord->ordered_features[i] = (f<<16)|s;
		}
	    }
	    if ( lastord==NULL )
		((SplineFont1 *) sf)->orders = ord;
	    else
		lastord->next = ord;
	    lastord = ord;
	} else if ( strmatch(tok,"BeginPrivate:")==0 ) {
	    SFDGetPrivate(sfd,sf);
	} else if ( strmatch(tok,"BeginSubrs:")==0 ) {	/* leave in so we don't croak on old sfd files */
	    SFDGetSubrs(sfd);
	} else if ( strmatch(tok,"PickledData:")==0 ) {
	    if (sf->python_persistent != NULL) {
#if defined(_NO_PYTHON)
	      free( sf->python_persistent );	/* It's a string of pickled data which we leave as a string */
#else
	      PyFF_FreePythonPersistent(sf->python_persistent);
#endif
	      sf->python_persistent = NULL;
	    }
	    sf->python_persistent = SFDUnPickle(sfd, 0);
	    sf->python_persistent_has_lists = 0;
	} else if ( strmatch(tok,"PickledDataWithLists:")==0 ) {
	    if (sf->python_persistent != NULL) {
#if defined(_NO_PYTHON)
	      free( sf->python_persistent );	/* It's a string of pickled data which we leave as a string */
#else
	      PyFF_FreePythonPersistent(sf->python_persistent);
#endif
	      sf->python_persistent = NULL;
	    }
	    sf->python_persistent = SFDUnPickle(sfd, 1);
	    sf->python_persistent_has_lists = 1;
	} else if ( strmatch(tok,"MMCounts:")==0 ) {
	    MMSet *mm = sf->mm = chunkalloc(sizeof(MMSet));
	    getint(sfd,&mm->instance_count);
	    getint(sfd,&mm->axis_count);
	    ch = nlgetc(sfd);
	    if ( ch!=' ' )
		ungetc(ch,sfd);
	    else { int temp;
		getint(sfd,&temp);
		mm->apple = temp;
		getint(sfd,&mm->named_instance_count);
	    }
	    mm->instances = calloc(mm->instance_count,sizeof(SplineFont *));
	    mm->positions = malloc(mm->instance_count*mm->axis_count*sizeof(real));
	    mm->defweights = malloc(mm->instance_count*sizeof(real));
	    mm->axismaps = calloc(mm->axis_count,sizeof(struct axismap));
	    if ( mm->named_instance_count!=0 )
		mm->named_instances = calloc(mm->named_instance_count,sizeof(struct named_instance));
	} else if ( strmatch(tok,"MMAxis:")==0 ) {
	    MMSet *mm = sf->mm;
	    if ( mm!=NULL ) {
		for ( i=0; i<mm->axis_count; ++i ) {
		    getname(sfd,tok);
		    mm->axes[i] = copy(tok);
		}
	    }
	} else if ( strmatch(tok,"MMPositions:")==0 ) {
	    MMSet *mm = sf->mm;
	    if ( mm!=NULL ) {
		for ( i=0; i<mm->axis_count*mm->instance_count; ++i )
		    getreal(sfd,&mm->positions[i]);
	    }
	} else if ( strmatch(tok,"MMWeights:")==0 ) {
	    MMSet *mm = sf->mm;
	    if ( mm!=NULL ) {
		for ( i=0; i<mm->instance_count; ++i )
		    getreal(sfd,&mm->defweights[i]);
	    }
	} else if ( strmatch(tok,"MMAxisMap:")==0 ) {
	    MMSet *mm = sf->mm;
	    if ( mm!=NULL ) {
		int index, points;
		getint(sfd,&index); getint(sfd,&points);
		mm->axismaps[index].points = points;
		mm->axismaps[index].blends = malloc(points*sizeof(real));
		mm->axismaps[index].designs = malloc(points*sizeof(real));
		for ( i=0; i<points; ++i ) {
		    getreal(sfd,&mm->axismaps[index].blends[i]);
		    while ( (ch=nlgetc(sfd))!=EOF && isspace(ch));
		    ungetc(ch,sfd);
		    if ( (ch=nlgetc(sfd))!='=' )
			ungetc(ch,sfd);
		    else if ( (ch=nlgetc(sfd))!='>' )
			ungetc(ch,sfd);
		    getreal(sfd,&mm->axismaps[index].designs[i]);
		}
		lastaxismap = &mm->axismaps[index];
		lastnamedinstance = NULL;
	    }
	} else if ( strmatch(tok,"MMNamedInstance:")==0 ) {
	    MMSet *mm = sf->mm;
	    if ( mm!=NULL ) {
		int index;
		getint(sfd,&index);
		mm->named_instances[index].coords = malloc(mm->axis_count*sizeof(real));
		for ( i=0; i<mm->axis_count; ++i )
		    getreal(sfd,&mm->named_instances[index].coords[i]);
		lastnamedinstance = &mm->named_instances[index];
		lastaxismap = NULL;
	    }
	} else if ( strmatch(tok,"MacName:")==0 ) {
	    struct macname *names = SFDParseMacNames(sfd,tok);
	    if ( lastaxismap!=NULL )
		lastaxismap->axisnames = names;
	    else if ( lastnamedinstance !=NULL )
		lastnamedinstance->names = names;
	    pushedbacktok = true;
	} else if ( strmatch(tok,"MMCDV:")==0 ) {
	    MMSet *mm = sf->mm;
	    if ( mm!=NULL )
		mm->cdv = SFDParseMMSubroutine(sfd);
	} else if ( strmatch(tok,"MMNDV:")==0 ) {
	    MMSet *mm = sf->mm;
	    if ( mm!=NULL )
		mm->ndv = SFDParseMMSubroutine(sfd);
	} else if ( strmatch(tok,"BeginMMFonts:")==0 ) {
	    int cnt;
	    getint(sfd,&cnt);
	    getint(sfd,&realcnt);
	    ff_progress_change_stages(cnt);
	    ff_progress_change_total(realcnt);
	    MMInferStuff(sf->mm);
    break;
	} else if ( strmatch(tok,"BeginSubFonts:")==0 ) {
	    getint(sfd,&sf->subfontcnt);
	    sf->subfonts = calloc(sf->subfontcnt,sizeof(SplineFont *));
	    getint(sfd,&realcnt);
	    sf->map = EncMap1to1(realcnt);
	    ff_progress_change_stages(2);
	    ff_progress_change_total(realcnt);
    break;
	} else if ( strmatch(tok,"BeginChars:")==0 ) {
	    int charcnt;
	    getint(sfd,&charcnt);
	    if (charcnt<enc->char_cnt) {
		IError("SFD file specifies too few slots for its encoding.\n" );
exit( 1 );
	    }
	    if ( getint(sfd,&realcnt)!=1 || realcnt==-1 )
		realcnt = charcnt;
	    else
		++realcnt;		/* value saved is max glyph, not glyph cnt */
	    ff_progress_change_total(realcnt);
	    sf->glyphcnt = sf->glyphmax = realcnt;
	    sf->glyphs = calloc(realcnt,sizeof(SplineChar *));
	    if ( cidmaster!=NULL ) {
		sf->map = cidmaster->map;
	    } else {
		sf->map = EncMapNew(charcnt,realcnt,enc);
		sf->map->remap = remap;
	    }
	    SFDSizeMap(sf->map,sf->glyphcnt,charcnt);
    break;
#if HANYANG
	} else if ( strmatch(tok,"BeginCompositionRules")==0 ) {
	    sf->rules = SFDReadCompositionRules(sfd);
#endif
	} else {
	    /* If we don't understand it, skip it */
	    geteol(sfd,tok);
	}
    }

    // Many downstream functions assume this isn't NULL (use strlen, etc.)
    if ( sf->fontname==NULL)
	sf->fontname = copy("");

    if ( fromdir )
	sf = SFD_FigureDirType(sf,tok,dirname,enc,remap,had_layer_cnt);
    else if ( sf->subfontcnt!=0 ) {
	ff_progress_change_stages(2*sf->subfontcnt);
	for ( i=0; i<sf->subfontcnt; ++i ) {
	    if ( i!=0 )
		ff_progress_next_stage();
	    sf->subfonts[i] = SFD_GetFont(sfd,sf,tok,fromdir,dirname,sfdversion);
	}
    } else if ( sf->mm!=NULL ) {
	MMSet *mm = sf->mm;
	ff_progress_change_stages(2*(mm->instance_count+1));
	for ( i=0; i<mm->instance_count; ++i ) {
	    if ( i!=0 )
		ff_progress_next_stage();
	    mm->instances[i] = SFD_GetFont(sfd,NULL,tok,fromdir,dirname,sfdversion);
	    EncMapFree(mm->instances[i]->map); mm->instances[i]->map=NULL;
	    mm->instances[i]->mm = mm;
	}
	ff_progress_next_stage();
	mm->normal = SFD_GetFont(sfd,NULL,tok,fromdir,dirname,sfdversion);
	mm->normal->mm = mm;
	sf->mm = NULL;
	SplineFontFree(sf);
	sf = mm->normal;
	if ( sf->map->enc!=&custom ) {
	    EncMap *map;
	    MMMatchGlyphs(mm);		/* sfd files from before the encoding change can have mismatched orig pos */
	    map = EncMapFromEncoding(sf,sf->map->enc);
	    EncMapFree(sf->map);
	    sf->map = map;
	}
    } else {
	while ( SFDGetChar(sfd,sf,had_layer_cnt)!=NULL ) {
	    ff_progress_next();
	}
	ff_progress_next_stage();
    }
    haddupenc = false;
    while ( getname(sfd,tok)==1 ) {
	if ( strcmp(tok,"EndSplineFont")==0 || strcmp(tok,"EndSubSplineFont")==0 )
    break;
	else if ( strcmp(tok,"BitmapFont:")==0 )
	    SFDGetBitmapFont(sfd,sf,false,NULL);
	else if ( strmatch(tok,"DupEnc:")==0 ) {
	    int enc, orig;
	    haddupenc = true;
	    if ( getint(sfd,&enc) && getint(sfd,&orig) && sf->map!=NULL ) {
		SFDSetEncMap(sf,orig,enc);
	    }
	}
    }
    if ( sf->cidmaster==NULL )
	SFDFixupRefs(sf);

    if ( !haddupenc )
	SFD_DoAltUnis(sf);
    else
	AltUniFigure(sf,sf->map,true);
    if ( sf->sfd_version<2 )
	SFD_AssignLookups((SplineFont1 *) sf);
    if ( !d.hadtimes )
	SFTimesFromFile(sf,sfd);
    // Make a blank encoding if there are no characters so as to avoid crashes later.
    if (sf->map == NULL) sf->map = EncMapNew(sf->glyphcnt,sf->glyphcnt,&custom);

    SFDFixupUndoRefs(sf);
return( sf );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };

#define GLYPH_EXT	".glyph"


static void SFDFixupRef(SplineChar *sc,RefChar *ref,int layer) {
    RefChar *rf;
    int ly;

    if ( sc->parent->multilayer ) {
	for ( ly=ly_fore; ly<ref->sc->layer_cnt; ++ly ) {
	    for ( rf = ref->sc->layers[ly].refs; rf!=NULL; rf=rf->next ) {
		if ( rf->sc==sc ) {	/* Huh? */
		    ref->sc->layers[ly].refs = NULL;
	    break;
		}
		if ( rf->layers[0].splines==NULL )
		    SFDFixupRef(ref->sc,rf,layer);
	    }
	}
    } else {
	for ( rf = ref->sc->layers[layer].refs; rf!=NULL; rf=rf->next ) {
	    if ( rf->sc==sc ) {	/* Huh? */
		ref->sc->layers[layer].refs = NULL;
	break;
	    }
	    if ( rf->layers[0].splines==NULL )
		SFDFixupRef(ref->sc,rf,layer);
	}
    }
    SCReinstanciateRefChar(sc,ref,layer);
    SCMakeDependent(sc,ref->sc);
}

Fix for #4084 Use-after-free (heap) in the SFD_GetFontMetaData() function
Fix for #4086 NULL pointer dereference in the SFDGetSpiros() function
Fix for #4088 NULL pointer dereference in the SFD_AssignLookups() function
Add empty sf->fontname string if it isn't set, fixing #4089 #4090 and many
  other potential issues (many downstream calls to strlen() on the value).

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };

static char base64[64] = {
 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

static const char *end_tt_instrs = "EndTTInstrs";

static void SFDConsumeUntil( FILE *sfd, const char** terminators );

static void SFDDumpRefs(FILE *sfd,RefChar *refs, int *newgids);

static void SFDDumpGuidelines(FILE *sfd,GuidelineSet *gl);

static RefChar *SFDGetRef(FILE *sfd, int was_enc);

static void SFDDumpImage(FILE *sfd,ImageList *img);

static void SFDDumpImagePNG(FILE *sfd,ImageList *img);

static AnchorPoint *SFDReadAnchorPoints(FILE *sfd,SplineChar *sc,AnchorPoint** alist, AnchorPoint *lastap);


static void SFDDumpPrivate(FILE *sfd,struct psdict *private) {
    int i;
    char *pt;
    /* These guys should all be ascii text */
    fprintf( sfd, "BeginPrivate: %d\n", private->next );
    for ( i=0; i<private->next ; ++i ) {
      fprintf( sfd, "%s %d ", private->keys[i],
	       (int)strlen(private->values[i]));
	for ( pt = private->values[i]; *pt; ++pt )
	    putc(*pt,sfd);
	putc('\n',sfd);
    }
    fprintf( sfd, "EndPrivate\n" );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };


static void SFDDumpRefs(FILE *sfd,RefChar *refs, int *newgids) {
    RefChar *ref;

    for ( ref=refs; ref!=NULL; ref=ref->next ) if ( ref->sc!=NULL ) {
	fprintf(sfd, "Refer: %d %d %c %g %g %g %g %g %g %d",
		    newgids!=NULL ? newgids[ref->sc->orig_pos]:ref->sc->orig_pos,
		    ref->sc->unicodeenc,
		    ref->selected?'S':'N',
		    (double) ref->transform[0], (double) ref->transform[1], (double) ref->transform[2],
		    (double) ref->transform[3], (double) ref->transform[4], (double) ref->transform[5],
		    ref->use_my_metrics|(ref->round_translation_to_grid<<1)|
		     (ref->point_match<<2));
	if ( ref->point_match ) {
	    fprintf(sfd, " %d %d", ref->match_pt_base, ref->match_pt_ref );
	    if ( ref->point_match_out_of_date )
		fprintf( sfd, " O" );
	}
	putc('\n',sfd);
    }
}


static void ASMReplaceTagsWithLookups(ASM *sm,SplineFont1 *sf) {
    int i;

    if ( sm->type != asm_context )
return;
    for ( i=0; i<sm->class_cnt*sm->state_cnt; ++i ) {
	if ( sm->state[i].u.context.mark_lookup!=NULL )
	    sm->state[i].u.context.mark_lookup = FindNestedLookupByTag(sf,(uint32) (intpt) (sm->state[i].u.context.mark_lookup) );
	if ( sm->state[i].u.context.cur_lookup!=NULL )
	    sm->state[i].u.context.cur_lookup = FindNestedLookupByTag(sf,(uint32) (intpt) (sm->state[i].u.context.cur_lookup) );
    }
}

/* No explicit global definitions found. */


void visitSFDFragment( FILE *sfd, SplineFont *sf,
		       visitSFDFragmentFunc ufunc, void* udata )
{
    int eof;
    char tok[2000];
    while ( 1 ) {
	if ( (eof = getname(sfd,tok))!=1 ) {
	    if ( eof==-1 )
		break;
	    geteol(sfd,tok);
	    continue;
	}

	ufunc( sfd, tok, sf, udata );
    }
}


int SFDDoesAnyBackupExist(char* filename)
{
    char path[PATH_MAX];
    int idx = 1;

    snprintf( path, PATH_MAX, "%s-%02d", filename, idx );
    return GFileExists(path);
}


void SFD_GetFontMetaDataData_Init( SFD_GetFontMetaDataData* d )
{
    memset( d, 0, sizeof(SFD_GetFontMetaDataData));
}

static void SFDConsumeUntil( FILE *sfd, const char** terminators );

static void SFDDumpRefs(FILE *sfd,RefChar *refs, int *newgids);

static void SFDDumpGuidelines(FILE *sfd,GuidelineSet *gl);

static void SFDDumpImage(FILE *sfd,ImageList *img);

static void SFDDumpImagePNG(FILE *sfd,ImageList *img);

static void SFDDumpTtfInstrs(FILE *sfd,SplineChar *sc);

static void SFDDumpTtfInstrsExplicit(FILE *sfd,uint8 *ttf_instrs, int16 ttf_instrs_len );

static void SFDDumpHintList(FILE *sfd,const char *key, StemInfo *h);

static void SFDDumpDHintList( FILE *sfd,const char *key, DStemInfo *d );


static void rle2image(struct enc85 *dec,int rlelen,struct _GImage *base) {
    uint8 *pt, *end;
    int r,c,set, cnt, ch, ch2;
    int i;

    r = c = 0; set = 1; pt = base->data; end = pt + base->bytes_per_line*base->height;
    memset(base->data,0xff,end-pt);
    while ( rlelen>0 ) {
	if ( pt>=end ) {
	    IError( "RLE failure\n" );
	    while ( rlelen>0 ) { Dec85(dec); --rlelen; }
    break;
	}
	ch = Dec85(dec);
	--rlelen;
	if ( ch==255 ) {
	    ch2 = Dec85(dec);
	    cnt = (ch2<<8) + Dec85(dec);
	    rlelen -= 2;
	} else
	    cnt = ch;
	if ( ch==255 && ch2==0 && cnt<255 ) {
	    /* Line duplication */
	    for ( i=0; i<cnt && pt<end; ++i ) {
		memcpy(pt,base->data+(r-1)*base->bytes_per_line,base->bytes_per_line);
		++r;
		pt += base->bytes_per_line;
	    }
	    set = 1;
	} else {
	    if ( pt + ((c+cnt)>>3) > end ) {
		IError( "Run length encoded image has been corrupted.\n" );
    break;
	    }
	    if ( !set ) {
		for ( i=0; i<cnt; ++i )
		    pt[(c+i)>>3] &= ((~0x80)>>((c+i)&7));
	    }
	    c += cnt;
	    set = 1-set;
	    if ( c>=base->width ) {
		++r;
		pt += base->bytes_per_line;
		c = 0; set = 1;
	    }
	}
    }
}


void SFD_GetFontMetaDataVoid( FILE *sfd,
			  char *tok,
			  SplineFont *sf,
			  void* d ) {
  SFD_GetFontMetaData(sfd, tok, sf, d);
}

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };


static uint8 *image2rle(struct _GImage *img, int *len) {
    int max = img->height*img->bytes_per_line;
    uint8 *rle, *pt, *end;
    int cnt, set;
    int i,j,k;

    *len = 0;
    if ( img->image_type!=it_mono || img->bytes_per_line<5 )
return( NULL );
    rle = calloc(max,sizeof(uint8)), pt = rle, end=rle+max-3;

    for ( i=0; i<img->height; ++i ) {
	if ( i!=0 ) {
	    if ( memcmp(img->data+i*img->bytes_per_line,
			img->data+(i-1)*img->bytes_per_line, img->bytes_per_line)== 0 ) {
		for ( k=1; k<img->height-i; ++k ) {
		    if ( memcmp(img->data+(i+k)*img->bytes_per_line,
				img->data+i*img->bytes_per_line, img->bytes_per_line)!= 0 )
		break;
		}
		i+=k;
		while ( k>0 ) {
		    if ( pt>end ) {
			free(rle);
return( NULL );
		    }
		    *pt++ = 255;
		    *pt++ = 0;
		    *pt++ = k>254 ? 254 : k;
		    k -= 254;
		}
		if ( i>=img->height )
    break;
	    }
	}

	set=1; cnt=0; j=0;
	while ( j<img->width ) {
	    for ( k=j; k<img->width; ++k ) {
		if (( set && !(img->data[i*img->bytes_per_line+(k>>3)]&(0x80>>(k&7))) ) ||
		    ( !set && (img->data[i*img->bytes_per_line+(k>>3)]&(0x80>>(k&7))) ))
	    break;
	    }
	    cnt = k-j;
	    j=k;
	    do {
		if ( pt>=end ) {
		    free(rle);
return( NULL );
		}
		if ( cnt<=254 )
		    *pt++ = cnt;
		else {
		    *pt++ = 255;
		    if ( cnt>65535 ) {
			*pt++ = 255;
			*pt++ = 255;
			*pt++ = 0;		/* nothing of the other color, we've still got more of this one */
		    } else {
			*pt++ = cnt>>8;
			*pt++ = cnt&0xff;
		    }
		}
		cnt -= 65535;
	    } while ( cnt>0 );
	    set = 1-set;
	}
    }
    *len = pt-rle;
return( rle );
}


static struct baselangextent *ParseBaseLang(FILE *sfd) {
    struct baselangextent *bl;
    struct baselangextent *cur, *last;
    int ch;

    while ( (ch=nlgetc(sfd))==' ' );
    if ( ch=='{' ) {
	bl = chunkalloc(sizeof(struct baselangextent));
	while ( (ch=nlgetc(sfd))==' ' );
	ungetc(ch,sfd);
	if ( ch=='\'' )
	    bl->lang = gettag(sfd);		/* Lang or Feature tag, or nothing */
	getsint(sfd,&bl->descent);
	getsint(sfd,&bl->ascent);
	last = NULL;
	while ( (ch=nlgetc(sfd))==' ' );
	while ( ch=='{' ) {
	    ungetc(ch,sfd);
	    cur = ParseBaseLang(sfd);
	    if ( last==NULL )
		bl->features = cur;
	    else
		last->next = cur;
	    last = cur;
	    while ( (ch=nlgetc(sfd))==' ' );
	}
	if ( ch!='}' ) ungetc(ch,sfd);
return( bl );
    }
return( NULL );
}


static int order_lookups(const void *_otl1, const void *_otl2) {
    const OTLookup *otl1 = *(const OTLookup **) _otl1, *otl2 = *(const OTLookup **) _otl2;
return( otl1->lookup_index - otl2->lookup_index );
}


static SplineChar *SFDGetChar(FILE *sfd,SplineFont *sf, int had_sf_layer_cnt) {
    SplineChar *sc;
    char tok[2000], ch;
    RefChar *lastr=NULL, *ref;
    ImageList *lasti=NULL, *img;
    AnchorPoint *lastap = NULL;
    GuidelineSet *lastgl = NULL;
    int isliga = 0, ispos, issubs, ismult, islcar, ispair, temp, i;
    PST *last = NULL;
    uint32 script = 0;
    int current_layer = ly_fore;
    int multilayer = sf->multilayer;
    int had_old_dstems = false;
    SplineFont *sli_sf = sf->cidmaster ? sf->cidmaster : sf;
    struct altuni *altuni;
    int oldback = false;

    if ( getname(sfd,tok)!=1 )
return( NULL );
    if ( strcmp(tok,"StartChar:")!=0 )
return( NULL );
    while ( isspace(ch=nlgetc(sfd)));
    ungetc(ch,sfd);
    sc = SFSplineCharCreate(sf);
    if ( ch!='"' ) {
	if ( getname(sfd,tok)!=1 ) {
	    SplineCharFree(sc);
return( NULL );
	}
	sc->name = copy(tok);
    } else {
	sc->name = SFDReadUTF7Str(sfd);
	if ( sc->name==NULL ) {
	    SplineCharFree(sc);
return( NULL );
	}
    }
    sc->vwidth = sf->ascent+sf->descent;
    sc->parent = sf;
    while ( 1 ) {
	if ( getname(sfd,tok)!=1 ) {
	    SplineCharFree(sc);
return( NULL );
	}
	if ( strmatch(tok,"Encoding:")==0 ) {
	    int enc;
	    getint(sfd,&enc);
	    getint(sfd,&sc->unicodeenc);
	    while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
	    ungetc(ch,sfd);
	    if ( ch!='\n' && ch!='\r' ) {
		getint(sfd,&sc->orig_pos);
		if ( sc->orig_pos==65535 )
		    sc->orig_pos = orig_pos++;
		    /* An old mark meaning: "I don't know" */
		if ( sc->orig_pos<sf->glyphcnt && sf->glyphs[sc->orig_pos]!=NULL )
		    sc->orig_pos = sf->glyphcnt;
		if ( sc->orig_pos>=sf->glyphcnt ) {
		    if ( sc->orig_pos>=sf->glyphmax )
			sf->glyphs = realloc(sf->glyphs,(sf->glyphmax = sc->orig_pos+10)*sizeof(SplineChar *));
		    memset(sf->glyphs+sf->glyphcnt,0,(sc->orig_pos+1-sf->glyphcnt)*sizeof(SplineChar *));
		    sf->glyphcnt = sc->orig_pos+1;
		}
		if ( sc->orig_pos+1 > orig_pos )
		    orig_pos = sc->orig_pos+1;
	    } else if ( sf->cidmaster!=NULL ) {		/* In cid fonts the orig_pos is just the cid */
		sc->orig_pos = enc;
	    } else {
		sc->orig_pos = orig_pos++;
	    }
	    SFDSetEncMap(sf,sc->orig_pos,enc);
	} else if ( strmatch(tok,"AltUni:")==0 ) {
	    int uni;
	    while ( getint(sfd,&uni)==1 ) {
		altuni = chunkalloc(sizeof(struct altuni));
		altuni->unienc = uni;
		altuni->vs = -1;
		altuni->fid = 0;
		altuni->next = sc->altuni;
		sc->altuni = altuni;
	    }
	} else if ( strmatch(tok,"AltUni2:")==0 ) {
	    uint32 uni[3];
	    while ( gethexints(sfd,uni,3) ) {
		altuni = chunkalloc(sizeof(struct altuni));
		altuni->unienc = uni[0];
		altuni->vs = uni[1];
		altuni->fid = uni[2];
		altuni->next = sc->altuni;
		sc->altuni = altuni;
	    }
	} else if ( strmatch(tok,"OldEncoding:")==0 ) {
	    int old_enc;		/* Obsolete info */
	    getint(sfd,&old_enc);
        } else if ( strmatch(tok,"Script:")==0 ) {
	    /* Obsolete. But still used for parsing obsolete ligature/subs tags */
            while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
            if ( ch=='\n' || ch=='\r' )
                script = 0;
            else {
		ungetc(ch,sfd);
		script = gettag(sfd);
            }
	} else if ( strmatch(tok,"GlifName:")==0 ) {
            while ( isspace(ch=nlgetc(sfd)));
            ungetc(ch,sfd);
            if ( ch!='"' ) {
              if ( getname(sfd,tok)!=1 ) {
                LogError(_("Invalid glif name.\n"));
              }
	      sc->glif_name = copy(tok);
            } else {
	      sc->glif_name = SFDReadUTF7Str(sfd);
	      if ( sc->glif_name==NULL ) {
                LogError(_("Invalid glif name.\n"));
	      }
            }
	} else if ( strmatch(tok,"Width:")==0 ) {
	    getsint(sfd,&sc->width);
	} else if ( strmatch(tok,"VWidth:")==0 ) {
	    getsint(sfd,&sc->vwidth);
	} else if ( strmatch(tok,"GlyphClass:")==0 ) {
	    getint(sfd,&temp);
	    sc->glyph_class = temp;
	} else if ( strmatch(tok,"UnlinkRmOvrlpSave:")==0 ) {
	    getint(sfd,&temp);
	    sc->unlink_rm_ovrlp_save_undo = temp;
	} else if ( strmatch(tok,"InSpiro:")==0 ) {
	    getint(sfd,&temp);
	    sc->inspiro = temp;
	} else if ( strmatch(tok,"LigCaretCntFixed:")==0 ) {
	    getint(sfd,&temp);
	    sc->lig_caret_cnt_fixed = temp;
	} else if ( strmatch(tok,"Flags:")==0 ) {
	    while ( isspace(ch=nlgetc(sfd)) && ch!='\n' && ch!='\r');
	    while ( ch!='\n' && ch!='\r' ) {
		if ( ch=='H' ) sc->changedsincelasthinted=true;
		else if ( ch=='M' ) sc->manualhints = true;
		else if ( ch=='W' ) sc->widthset = true;
		else if ( ch=='O' ) sc->wasopen = true;
		else if ( ch=='I' ) sc->instructions_out_of_date = true;
		ch = nlgetc(sfd);
	    }
	    if ( sf->multilayer || sf->onlybitmaps || sf->strokedfont || sc->layers[ly_fore].order2 )
		sc->changedsincelasthinted = false;
	} else if ( strmatch(tok,"TeX:")==0 ) {
	    getsint(sfd,&sc->tex_height);
	    getsint(sfd,&sc->tex_depth);
	    while ( isspace(ch=nlgetc(sfd)) && ch!='\n' && ch!='\r');
	    ungetc(ch,sfd);
	    if ( ch!='\n' && ch!='\r' ) {
		int16 old_tex;
		/* Used to store two extra values here */
		getsint(sfd,&old_tex);
		getsint(sfd,&old_tex);
		if ( sc->tex_height==0 && sc->tex_depth==0 )		/* Fixup old bug */
		    sc->tex_height = sc->tex_depth = TEX_UNDEF;
	    }
	} else if ( strmatch(tok,"ItalicCorrection:")==0 ) {
	    SFDParseMathValueRecord(sfd,&sc->italic_correction,&sc->italic_adjusts);
	} else if ( strmatch(tok,"TopAccentHorizontal:")==0 ) {
	    SFDParseMathValueRecord(sfd,&sc->top_accent_horiz,&sc->top_accent_adjusts);
	} else if ( strmatch(tok,"GlyphCompositionVerticalIC:")==0 ) {
	    if ( sc->vert_variants==NULL )
		sc->vert_variants = chunkalloc(sizeof(struct glyphvariants));
	    SFDParseMathValueRecord(sfd,&sc->vert_variants->italic_correction,&sc->vert_variants->italic_adjusts);
	} else if ( strmatch(tok,"GlyphCompositionHorizontalIC:")==0 ) {
	    if ( sc->horiz_variants==NULL )
		sc->horiz_variants = chunkalloc(sizeof(struct glyphvariants));
	    SFDParseMathValueRecord(sfd,&sc->horiz_variants->italic_correction,&sc->horiz_variants->italic_adjusts);
	} else if ( strmatch(tok,"IsExtendedShape:")==0 ) {
	    int temp;
	    getint(sfd,&temp);
	    sc->is_extended_shape = temp;
	} else if ( strmatch(tok,"GlyphVariantsVertical:")==0 ) {
	    if ( sc->vert_variants==NULL )
		sc->vert_variants = chunkalloc(sizeof(struct glyphvariants));
	    geteol(sfd,tok);
	    sc->vert_variants->variants = copy(tok);
	} else if ( strmatch(tok,"GlyphVariantsHorizontal:")==0 ) {
	    if ( sc->horiz_variants==NULL )
		sc->horiz_variants = chunkalloc(sizeof(struct glyphvariants));
	    geteol(sfd,tok);
	    sc->horiz_variants->variants = copy(tok);
	} else if ( strmatch(tok,"GlyphCompositionVertical:")==0 ) {
	    sc->vert_variants = SFDParseGlyphComposition(sfd, sc->vert_variants,tok);
	} else if ( strmatch(tok,"GlyphCompositionHorizontal:")==0 ) {
	    sc->horiz_variants = SFDParseGlyphComposition(sfd, sc->horiz_variants,tok);
	} else if ( strmatch(tok,"TopRightVertex:")==0 ) {
	    if ( sc->mathkern==NULL )
		sc->mathkern = chunkalloc(sizeof(struct mathkern));
	    SFDParseVertexKern(sfd, &sc->mathkern->top_right);
	} else if ( strmatch(tok,"TopLeftVertex:")==0 ) {
	    if ( sc->mathkern==NULL )
		sc->mathkern = chunkalloc(sizeof(struct mathkern));
	    SFDParseVertexKern(sfd, &sc->mathkern->top_left);
	} else if ( strmatch(tok,"BottomRightVertex:")==0 ) {
	    if ( sc->mathkern==NULL )
		sc->mathkern = chunkalloc(sizeof(struct mathkern));
	    SFDParseVertexKern(sfd, &sc->mathkern->bottom_right);
	} else if ( strmatch(tok,"BottomLeftVertex:")==0 ) {
	    if ( sc->mathkern==NULL )
		sc->mathkern = chunkalloc(sizeof(struct mathkern));
	    SFDParseVertexKern(sfd, &sc->mathkern->bottom_left);
#if HANYANG
	} else if ( strmatch(tok,"CompositionUnit:")==0 ) {
	    getsint(sfd,&sc->jamo);
	    getsint(sfd,&sc->varient);
	    sc->compositionunit = true;
#endif
	} else if ( strmatch(tok,"HStem:")==0 ) {
	    sc->hstem = SFDReadHints(sfd);
	    sc->hconflicts = StemListAnyConflicts(sc->hstem);
	} else if ( strmatch(tok,"VStem:")==0 ) {
	    sc->vstem = SFDReadHints(sfd);
	    sc->vconflicts = StemListAnyConflicts(sc->vstem);
	} else if ( strmatch(tok,"DStem:")==0 ) {
	    sc->dstem = SFDReadDHints( sc->parent,sfd,true );
            had_old_dstems = true;
	} else if ( strmatch(tok,"DStem2:")==0 ) {
	    sc->dstem = SFDReadDHints( sc->parent,sfd,false );
	} else if ( strmatch(tok,"CounterMasks:")==0 ) {
	    getsint(sfd,&sc->countermask_cnt);
	    sc->countermasks = calloc(sc->countermask_cnt,sizeof(HintMask));
	    for ( i=0; i<sc->countermask_cnt; ++i ) {
		int ch;
		while ( (ch=nlgetc(sfd))==' ' );
		ungetc(ch,sfd);
		SFDGetHintMask(sfd,&sc->countermasks[i]);
	    }
	} else if ( strmatch(tok,"AnchorPoint:")==0 ) {
	    lastap = SFDReadAnchorPoints(sfd,sc,&sc->anchor,lastap);
	} else if ( strmatch(tok,"Fore")==0 ) {
	    while ( isspace(ch = nlgetc(sfd)));
	    ungetc(ch,sfd);
	    if ( ch!='I' && ch!='R' && ch!='S' && ch!='V' && ch!=' ' && ch!='\n' &&
	         !PeekMatch(sfd, "Pickled") && !PeekMatch(sfd, "EndChar") &&
	         !PeekMatch(sfd, "Fore") && !PeekMatch(sfd, "Back") && !PeekMatch(sfd, "Layer") ) {
		/* Old format, without a SplineSet token */
		sc->layers[ly_fore].splines = SFDGetSplineSet(sfd,sc->layers[ly_fore].order2);
	    }
	    current_layer = ly_fore;
	    lastgl = NULL;
	} else if ( strmatch(tok,"MinimumDistance:")==0 ) {
	    SFDGetMinimumDistances(sfd,sc);
	} else if ( strmatch(tok,"Validated:")==0 ) {
	    getsint(sfd,(int16 *) &sc->layers[current_layer].validation_state);
	} else if ( strmatch(tok,"Back")==0 ) {
	    while ( isspace(ch=nlgetc(sfd)));
	    ungetc(ch,sfd);
	    if ( ch!='I' && ch!='R' && ch!='S' && ch!='V' && ch!=' ' && ch!='\n' &&
	         !PeekMatch(sfd, "Pickled") && !PeekMatch(sfd, "EndChar") &&
	         !PeekMatch(sfd, "Fore") && !PeekMatch(sfd, "Back") && !PeekMatch(sfd, "Layer") ) {
		/* Old format, without a SplineSet token */
		sc->layers[ly_back].splines = SFDGetSplineSet(sfd,sc->layers[ly_back].order2);
		oldback = true;
	    }
	    current_layer = ly_back;
	    lastgl = NULL;
	} else if ( strmatch(tok,"LayerCount:")==0 ) {
	    getint(sfd,&temp);
	    if ( temp>sc->layer_cnt ) {
		sc->layers = realloc(sc->layers,temp*sizeof(Layer));
		memset(sc->layers+sc->layer_cnt,0,(temp-sc->layer_cnt)*sizeof(Layer));
	    }
	    sc->layer_cnt = temp;
	    current_layer = ly_fore;
	} else if ( strmatch(tok,"Layer:")==0 ) {
	    int layer;
	    int dofill, dostroke, fillfirst, linejoin, linecap;
	    uint32 fillcol, strokecol;
	    real fillopacity, strokeopacity, strokewidth, trans[4];
	    DashType dashes[DASH_MAX];
	    int i;
	    getint(sfd,&layer);
	    if ( layer>=sc->layer_cnt ) {
		sc->layers = realloc(sc->layers,(layer+1)*sizeof(Layer));
		memset(sc->layers+sc->layer_cnt,0,(layer+1-sc->layer_cnt)*sizeof(Layer));
	    }
	    if ( sc->parent->multilayer ) {
		getint(sfd,&dofill);
		getint(sfd,&dostroke);
		getint(sfd,&fillfirst);
		gethex(sfd,&fillcol);
		getreal(sfd,&fillopacity);
		gethex(sfd,&strokecol);
		getreal(sfd,&strokeopacity);
		getreal(sfd,&strokewidth);
		getname(sfd,tok);
		for ( i=0; joins[i]!=NULL; ++i )
		    if ( strmatch(joins[i],tok)==0 )
		break;
		if ( joins[i]==NULL ) --i;
		linejoin = i;
		getname(sfd,tok);
		for ( i=0; caps[i]!=NULL; ++i )
		    if ( strmatch(caps[i],tok)==0 )
		break;
		if ( caps[i]==NULL ) --i;
		linecap = i;
		while ( (ch=nlgetc(sfd))==' ' || ch=='[' );
		ungetc(ch,sfd);
		getreal(sfd,&trans[0]);
		getreal(sfd,&trans[1]);
		getreal(sfd,&trans[2]);
		getreal(sfd,&trans[3]);
		while ( (ch=nlgetc(sfd))==' ' || ch==']' );
		if ( ch=='[' ) {
		    for ( i=0;; ++i ) { int temp;
			if ( !getint(sfd,&temp) )
		    break;
			else if ( i<DASH_MAX )
			    dashes[i] = temp;
		    }
		    if ( i<DASH_MAX )
			dashes[i] = 0;
		} else {
		    ungetc(ch,sfd);
		    memset(dashes,0,sizeof(dashes));
		}
		sc->layers[layer].dofill = dofill;
		sc->layers[layer].dostroke = dostroke;
		sc->layers[layer].fillfirst = fillfirst;
		sc->layers[layer].fill_brush.col = fillcol;
		sc->layers[layer].fill_brush.opacity = fillopacity;
		sc->layers[layer].stroke_pen.brush.col = strokecol;
		sc->layers[layer].stroke_pen.brush.opacity = strokeopacity;
		sc->layers[layer].stroke_pen.width = strokewidth;
		sc->layers[layer].stroke_pen.linejoin = linejoin;
		sc->layers[layer].stroke_pen.linecap = linecap;
		memcpy(sc->layers[layer].stroke_pen.dashes,dashes,sizeof(dashes));
		memcpy(sc->layers[layer].stroke_pen.trans,trans,sizeof(trans));
	    }
	    current_layer = layer;
	    lasti = NULL;
	    lastr = NULL;
	    lastgl = NULL;
	} else if ( strmatch(tok,"FillGradient:")==0 ) {
	    sc->layers[current_layer].fill_brush.gradient = SFDParseGradient(sfd,tok);
	} else if ( strmatch(tok,"FillPattern:")==0 ) {
	    sc->layers[current_layer].fill_brush.pattern = SFDParsePattern(sfd,tok);
	} else if ( strmatch(tok,"StrokeGradient:")==0 ) {
	    sc->layers[current_layer].stroke_pen.brush.gradient = SFDParseGradient(sfd,tok);
	} else if ( strmatch(tok,"StrokePattern:")==0 ) {
	    sc->layers[current_layer].stroke_pen.brush.pattern = SFDParsePattern(sfd,tok);
	} else if ( strmatch(tok,"UndoRedoHistory")==0 ) {

	    getname(sfd,tok);
	    if ( !strmatch(tok,"Layer:") ) {
		int layer;
		getint(sfd,&layer);
	    }

	    int limit;
	    Undoes *undo = 0;
	    struct undoes *last = 0;

	    getname(sfd,tok);
	    if ( !strmatch(tok,"Undoes") ) {
		undo = 0;
		limit = UndoRedoLimitToLoad;
		last = sc->layers[current_layer].undoes;
		while((undo = SFDGetUndo( sfd, sc, "UndoOperation", current_layer )))
		{
		    // push to back
		    if( last ) last->next = undo;
		    else       sc->layers[current_layer].undoes = undo;
		    last = undo;

		    if( limit != -1 ) {
			limit--;
			if( limit <= 0 ) {
			    // we have hit our load limit, so lets just chuck everything away
			    // until we hit the EndUndoes/EndRedoes magic line and then start
			    // actually processing again.
			    const char* terminators[] = { "EndUndoes", "EndRedoes", 0 };
			    SFDConsumeUntil( sfd, terminators );
			}
		    }
		}
	    }
	    getname(sfd,tok);
	    if ( !strmatch(tok,"Redoes") ) {
		undo = 0;
		limit = UndoRedoLimitToLoad;
		last = sc->layers[current_layer].redoes;
		while((undo = SFDGetUndo( sfd, sc, "RedoOperation", current_layer )))
		{
		    // push to back
		    if( last ) last->next = undo;
		    else       sc->layers[current_layer].redoes = undo;
		    last = undo;

		    if( limit != -1 ) {
			limit--;
			if( limit <= 0 ) {
			    // we have hit our load limit, so lets just chuck everything away
			    // until we hit the EndUndoes/EndRedoes magic line and then start
			    // actually processing again.
			    const char* terminators[] = { "EndUndoes", "EndRedoes", 0 };
			    SFDConsumeUntil( sfd, terminators );
			}
		    }
		}
	    }
	} else if ( strmatch(tok,"SplineSet")==0 ) {
	    sc->layers[current_layer].splines = SFDGetSplineSet(sfd,sc->layers[current_layer].order2);
	} else if ( strmatch(tok,"Guideline:")==0 ) {
	    lastgl = SFDReadGuideline(sfd, &sc->layers[current_layer].guidelines, lastgl);
	} else if ( strmatch(tok,"Ref:")==0 || strmatch(tok,"Refer:")==0 ) {
	    /* I should be depending on the version number here, but I made */
	    /*  a mistake and bumped the version too late. So the version is */
	    /*  not an accurate mark, but the presence of a LayerCount keyword*/
	    /*  in the font is an good mark. Before the LayerCount was added */
	    /*  (version 2) only the foreground layer could have references */
	    /*  after that (eventually version 3) any layer could. */
	    if ( oldback || !had_sf_layer_cnt ) current_layer = ly_fore;
	    ref = SFDGetRef(sfd,strmatch(tok,"Ref:")==0);
	    if ( sc->layers[current_layer].refs==NULL )
		sc->layers[current_layer].refs = ref;
	    else
		lastr->next = ref;
	    lastr = ref;
	} else if ( strmatch(tok,"Image:")==0 ) {
	    int ly = current_layer;
	    if ( !multilayer && !sc->layers[ly].background ) ly = ly_back;
	    img = SFDGetImage(sfd);
	    if (img != NULL) {
	    if ( sc->layers[ly].images==NULL )
		sc->layers[ly].images = img;
	    else
		lasti->next = img;
	    lasti = img;
	    }
	} else if ( strmatch(tok,"Image2:")==0 ) {
#ifndef _NO_LIBPNG
	    enum MIME mime = SFDGetImage2MIME(sfd);
	    if (mime == PNG) {
		int ly = current_layer;
		if ( !multilayer && !sc->layers[ly].background ) ly = ly_back;
		img = SFDGetImagePNG(sfd);
		if (img != NULL) {
		    if ( sc->layers[ly].images==NULL )
			sc->layers[ly].images = img;
		    else
			lasti->next = img;
		    lasti = img;
		}
	    } else
#endif
	    {
	    LogError(_("Image2 skipped as it uses an unsupported image type"));
	    const char* im2_terminator[] = { "EndImage2", 0 };
	    SFDConsumeUntil(sfd, im2_terminator);
	    }
	} else if ( strmatch(tok,"PickledData:")==0 ) {
	    if (current_layer < sc->layer_cnt) {
	      sc->layers[current_layer].python_persistent = SFDUnPickle(sfd, 0);
	      sc->layers[current_layer].python_persistent_has_lists = 0;
	    }
	} else if ( strmatch(tok,"PickledDataWithLists:")==0 ) {
	    if (current_layer < sc->layer_cnt) {
	      sc->layers[current_layer].python_persistent = SFDUnPickle(sfd, 1);
	      sc->layers[current_layer].python_persistent_has_lists = 1;
	    }
	} else if ( strmatch(tok,"OrigType1:")==0 ) {	/* Accept, slurp, ignore contents */
	    SFDGetType1(sfd);
	} else if ( strmatch(tok,"TtfInstrs:")==0 ) {	/* Binary format */
	    SFDGetTtfInstrs(sfd,sc);
	} else if ( strmatch(tok,"TtInstrs:")==0 ) {	/* ASCII format */
	    SFDGetTtInstrs(sfd,sc);
	} else if ( strmatch(tok,"Kerns2:")==0 ||
		strmatch(tok,"VKerns2:")==0 ) {
	    KernPair *kp, *last=NULL;
	    int isv = *tok=='V';
	    int off, index;
	    struct lookup_subtable *sub;

	    if ( sf->sfd_version<2 )
		LogError(_("Found an new style kerning pair inside a version 1 (or lower) sfd file.\n") );
	    while ( fscanf(sfd,"%d %d", &index, &off )==2 ) {
		sub = SFFindLookupSubtableAndFreeName(sf,SFDReadUTF7Str(sfd));
		if ( sub==NULL ) {
		    LogError(_("KernPair with no subtable name.\n"));
	    break;
		}
		kp = chunkalloc(sizeof(KernPair1));
		kp->sc = (SplineChar *) (intpt) index;
		kp->kcid = true;
		kp->off = off;
		kp->subtable = sub;
		kp->next = NULL;
		while ( (ch=nlgetc(sfd))==' ' );
		ungetc(ch,sfd);
		if ( ch=='{' ) {
		    kp->adjust = SFDReadDeviceTable(sfd, NULL);
		}
		if ( last != NULL )
		    last->next = kp;
		else if ( isv )
		    sc->vkerns = kp;
		else
		    sc->kerns = kp;
		last = kp;
	    }
	} else if ( strmatch(tok,"Kerns:")==0 ||
		strmatch(tok,"KernsSLI:")==0 ||
		strmatch(tok,"KernsSLIF:")==0 ||
		strmatch(tok,"VKernsSLIF:")==0 ||
		strmatch(tok,"KernsSLIFO:")==0 ||
		strmatch(tok,"VKernsSLIFO:")==0 ) {
	    KernPair1 *kp, *last=NULL;
	    int index, off, sli, flags=0;
	    int hassli = (strmatch(tok,"KernsSLI:")==0);
	    int isv = *tok=='V';
	    int has_orig = strstr(tok,"SLIFO:")!=NULL;
	    if ( sf->sfd_version>=2 ) {
		IError( "Found an old style kerning pair inside a version 2 (or higher) sfd file." );
exit(1);
	    }
	    if ( strmatch(tok,"KernsSLIF:")==0 || strmatch(tok,"KernsSLIFO:")==0 ||
		    strmatch(tok,"VKernsSLIF:")==0 || strmatch(tok,"VKernsSLIFO:")==0 )
		hassli=2;
	    while ( (hassli==1 && fscanf(sfd,"%d %d %d", &index, &off, &sli )==3) ||
		    (hassli==2 && fscanf(sfd,"%d %d %d %d", &index, &off, &sli, &flags )==4) ||
		    (hassli==0 && fscanf(sfd,"%d %d", &index, &off )==2) ) {
		if ( !hassli )
		    sli = SFFindBiggestScriptLangIndex(sli_sf,
			    script!=0?script:SCScriptFromUnicode(sc),DEFAULT_LANG);
		if ( sli>=((SplineFont1 *) sli_sf)->sli_cnt && sli!=SLI_NESTED) {
		    static int complained=false;
		    if ( !complained )
			IError("'%s' in %s has a script index out of bounds: %d",
				isv ? "vkrn" : "kern",
				sc->name, sli );
		    sli = SFFindBiggestScriptLangIndex(sli_sf,
			    SCScriptFromUnicode(sc),DEFAULT_LANG);
		    complained = true;
		}
		kp = chunkalloc(sizeof(KernPair1));
		kp->kp.sc = (SplineChar *) (intpt) index;
		kp->kp.kcid = has_orig;
		kp->kp.off = off;
		kp->sli = sli;
		kp->flags = flags;
		kp->kp.next = NULL;
		while ( (ch=nlgetc(sfd))==' ' );
		ungetc(ch,sfd);
		if ( ch=='{' ) {
		    kp->kp.adjust = SFDReadDeviceTable(sfd, NULL);
		}
		if ( last != NULL )
		    last->kp.next = (KernPair *) kp;
		else if ( isv )
		    sc->vkerns = (KernPair *) kp;
		else
		    sc->kerns = (KernPair *) kp;
		last = kp;
	    }
	} else if ( (ispos = (strmatch(tok,"Position:")==0)) ||
		( ispos  = (strmatch(tok,"Position2:")==0)) ||
		( ispair = (strmatch(tok,"PairPos:")==0)) ||
		( ispair = (strmatch(tok,"PairPos2:")==0)) ||
		( islcar = (strmatch(tok,"LCarets:")==0)) ||
		( islcar = (strmatch(tok,"LCarets2:")==0)) ||
		( isliga = (strmatch(tok,"Ligature:")==0)) ||
		( isliga = (strmatch(tok,"Ligature2:")==0)) ||
		( issubs = (strmatch(tok,"Substitution:")==0)) ||
		( issubs = (strmatch(tok,"Substitution2:")==0)) ||
		( ismult = (strmatch(tok,"MultipleSubs:")==0)) ||
		( ismult = (strmatch(tok,"MultipleSubs2:")==0)) ||
		strmatch(tok,"AlternateSubs:")==0 ||
		strmatch(tok,"AlternateSubs2:")==0 ) {
	    PST *pst;
	    int old, type;
	    type = ispos ? pst_position :
			 ispair ? pst_pair :
			 islcar ? pst_lcaret :
			 isliga ? pst_ligature :
			 issubs ? pst_substitution :
			 ismult ? pst_multiple :
			 pst_alternate;
	    if ( strchr(tok,'2')!=NULL ) {
		old = false;
		pst = chunkalloc(sizeof(PST));
		if ( type!=pst_lcaret )
		    pst->subtable = SFFindLookupSubtableAndFreeName(sf,SFDReadUTF7Str(sfd));
	    } else {
		old = true;
		pst = chunkalloc(sizeof(PST1));
		((PST1 *) pst)->tag = CHR('l','i','g','a');
		((PST1 *) pst)->script_lang_index = 0xffff;
		while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
		if ( isdigit(ch)) {
		    int temp;
		    ungetc(ch,sfd);
		    getint(sfd,&temp);
		    ((PST1 *) pst)->flags = temp;
		    while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
		} else
		    ((PST1 *) pst)->flags = 0 /*PSTDefaultFlags(type,sc)*/;
		if ( isdigit(ch)) {
		    ungetc(ch,sfd);
		    getusint(sfd,&((PST1 *) pst)->script_lang_index);
		    while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
		} else
		    ((PST1 *) pst)->script_lang_index = SFFindBiggestScriptLangIndex(sf,
			    script!=0?script:SCScriptFromUnicode(sc),DEFAULT_LANG);
		if ( ch=='\'' ) {
		    ungetc(ch,sfd);
		    ((PST1 *) pst)->tag = gettag(sfd);
		} else if ( ch=='<' ) {
		    getint(sfd,&temp);
		    ((PST1 *) pst)->tag = temp<<16;
		    nlgetc(sfd);	/* comma */
		    getint(sfd,&temp);
		    ((PST1 *) pst)->tag |= temp;
		    nlgetc(sfd);	/* close '>' */
		    ((PST1 *) pst)->macfeature = true;
		} else
		    ungetc(ch,sfd);
		if ( type==pst_lcaret ) {
		/* These are meaningless for lcarets, set them to innocuous values */
		    ((PST1 *) pst)->script_lang_index = SLI_UNKNOWN;
		    ((PST1 *) pst)->tag = CHR(' ',' ',' ',' ');
		} else if ( ((PST1 *) pst)->script_lang_index>=((SplineFont1 *) sli_sf)->sli_cnt && ((PST1 *) pst)->script_lang_index!=SLI_NESTED ) {
		    static int complained=false;
		    if ( !complained )
			IError("'%c%c%c%c' in %s has a script index out of bounds: %d",
				(((PST1 *) pst)->tag>>24), (((PST1 *) pst)->tag>>16)&0xff, (((PST1 *) pst)->tag>>8)&0xff, ((PST1 *) pst)->tag&0xff,
				sc->name, ((PST1 *) pst)->script_lang_index );
		    else
			IError( "'%c%c%c%c' in %s has a script index out of bounds: %d\n",
				(((PST1 *) pst)->tag>>24), (((PST1 *) pst)->tag>>16)&0xff, (((PST1 *) pst)->tag>>8)&0xff, ((PST1 *) pst)->tag&0xff,
				sc->name, ((PST1 *) pst)->script_lang_index );
		    ((PST1 *) pst)->script_lang_index = SFFindBiggestScriptLangIndex(sli_sf,
			    SCScriptFromUnicode(sc),DEFAULT_LANG);
		    complained = true;
		}
	    }
	    if ( (sf->sfd_version<2)!=old ) {
		IError( "Version mixup in PST of sfd file." );
exit(1);
	    }
	    if ( last==NULL )
		sc->possub = pst;
	    else
		last->next = pst;
	    last = pst;
	    pst->type = type;
	    if ( pst->type==pst_position ) {
		fscanf( sfd, " dx=%hd dy=%hd dh=%hd dv=%hd",
			&pst->u.pos.xoff, &pst->u.pos.yoff,
			&pst->u.pos.h_adv_off, &pst->u.pos.v_adv_off);
		pst->u.pos.adjust = SFDReadValDevTab(sfd);
		ch = nlgetc(sfd);		/* Eat new line */
	    } else if ( pst->type==pst_pair ) {
		getname(sfd,tok);
		pst->u.pair.paired = copy(tok);
		pst->u.pair.vr = chunkalloc(sizeof(struct vr [2]));
		fscanf( sfd, " dx=%hd dy=%hd dh=%hd dv=%hd",
			&pst->u.pair.vr[0].xoff, &pst->u.pair.vr[0].yoff,
			&pst->u.pair.vr[0].h_adv_off, &pst->u.pair.vr[0].v_adv_off);
		pst->u.pair.vr[0].adjust = SFDReadValDevTab(sfd);
		fscanf( sfd, " dx=%hd dy=%hd dh=%hd dv=%hd",
			&pst->u.pair.vr[1].xoff, &pst->u.pair.vr[1].yoff,
			&pst->u.pair.vr[1].h_adv_off, &pst->u.pair.vr[1].v_adv_off);
		pst->u.pair.vr[0].adjust = SFDReadValDevTab(sfd);
		ch = nlgetc(sfd);
	    } else if ( pst->type==pst_lcaret ) {
		int i;
		fscanf( sfd, " %d", &pst->u.lcaret.cnt );
		pst->u.lcaret.carets = malloc(pst->u.lcaret.cnt*sizeof(int16));
		for ( i=0; i<pst->u.lcaret.cnt; ++i )
		    fscanf( sfd, " %hd", &pst->u.lcaret.carets[i]);
		geteol(sfd,tok);
	    } else {
		geteol(sfd,tok);
		pst->u.lig.components = copy(tok);	/* it's in the same place for all formats */
		if ( isliga ) {
		    pst->u.lig.lig = sc;
		    if ( old )
			last = (PST *) LigaCreateFromOldStyleMultiple((PST1 *) pst);
		}
	    }
#ifdef FONTFORGE_CONFIG_CVT_OLD_MAC_FEATURES
	    if ( old )
		CvtOldMacFeature((PST1 *) pst);
#endif
	} else if ( strmatch(tok,"Colour:")==0 ) {
	    uint32 temp;
	    gethex(sfd,&temp);
	    sc->color = temp;
	} else if ( strmatch(tok,"Comment:")==0 ) {
	    sc->comment = SFDReadUTF7Str(sfd);
	} else if ( strmatch(tok,"Decomposition:")==0 ) {
	    char* decomp = SFDReadUTF7Str(sfd);
	    sc->user_decomp = utf82u_copy(decomp);
	    free(decomp);
	} else if ( strmatch(tok,"TileMargin:")==0 ) {
	    getreal(sfd,&sc->tile_margin);
	} else if ( strmatch(tok,"TileBounds:")==0 ) {
	    getreal(sfd,&sc->tile_bounds.minx);
	    getreal(sfd,&sc->tile_bounds.miny);
	    getreal(sfd,&sc->tile_bounds.maxx);
	    getreal(sfd,&sc->tile_bounds.maxy);
	} else if ( strmatch(tok,"EndChar")==0 ) {
	    if ( sc->orig_pos<sf->glyphcnt )
		sf->glyphs[sc->orig_pos] = sc;
            /* Recalculating hint active zones may be needed for old .sfd files. */
            /* Do this when we have finished with other glyph components, */
            /* so that splines are already available */
	    if ( sf->sfd_version<2 )
                SCGuessHintInstancesList( sc,ly_fore,sc->hstem,sc->vstem,sc->dstem,false,false );
            else if ( had_old_dstems && sc->layers[ly_fore].splines != NULL )
                SCGuessHintInstancesList( sc,ly_fore,NULL,NULL,sc->dstem,false,true );
	    if ( sc->layers[ly_fore].order2 )
		SCDefaultInterpolation(sc);
return( sc );
	} else {
	    geteol(sfd,tok);
	}
    }
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };

#define FONT_PROPS	"font.props"

#define GLYPH_EXT	".glyph"


char* FileToAllocatedString( FILE *f ) {
    char *ret, *buf;
    long fsize = 0;
    size_t bread = 0;

    /* get approximate file size, and allocate some memory */
    if ( fseek(f,0,SEEK_END)==0 && \
	 (fsize=ftell(f))!=-1   && \
	 fseek(f,0,SEEK_SET)==0 && \
	 (buf=calloc(fsize+30001,1))!=NULL ) {
	/* fread in file, size=non-exact, then resize memory smaller */
	bread=fread(buf,1,fsize+30000,f);
	if ( bread<=0 || bread >=(size_t)fsize+30000 || (ret=realloc(buf,bread+1))==NULL ) {
	    free( buf );
	} else {
	    ret[bread] = '\0';
	    return( ret );
	}
    }

    /* error occurred reading in file */
    fprintf(stderr,_("Failed to read a file. Bytes read:%ld file size:%ld\n"),(long)(bread),fsize );
    return( 0 );
}


static void SFDDumpImagePNG(FILE *sfd,ImageList *img) {
    struct enc85 enc = {0};
    char* pngbuf;
    size_t pnglen, i;

    if (!GImageWritePngBuf(img->image, &pngbuf, &pnglen, 1, false)) {
        IError("Failed to serialise PNG image");
        return;
    }

    fprintf(sfd, "Image2: image/png %d %g %g %g %g\n",
        (int)pnglen, (double) img->xoff, (double) img->yoff, (double) img->xscale, (double) img->yscale );

    enc.sfd = sfd;
    for (i = 0; i<pnglen; ++i) {
        SFDEnc85(&enc, pngbuf[i]);
    }
    free(pngbuf);

    SFDEnc85EndEnc(&enc);
    fprintf(sfd,"\nEndImage2\n" );
}


static void SFDDumpMathVertex(FILE *sfd,struct mathkernvertex *vert,const char *name) {
    int i;

    if ( vert==NULL || vert->cnt==0 )
return;

    fprintf( sfd, "%s %d ", name, vert->cnt );
    for ( i=0; i<vert->cnt; ++i ) {
	fprintf( sfd, " %d", vert->mkd[i].height );
	SFDDumpDeviceTable(sfd,vert->mkd[i].height_adjusts );
	fprintf( sfd, ",%d", vert->mkd[i].kern );
	SFDDumpDeviceTable(sfd,vert->mkd[i].kern_adjusts );
    }
    putc('\n',sfd );
}


char* SFDMoveToNextStartChar( FILE* sfd ) {
    char ret[2000];

    memset( ret, '\0', 2000 );
    char* line = 0;
    while((line = getquotedeol( sfd ))) {
	if( !strnmatch( line, "StartChar:", strlen( "StartChar:" ))) {
	    // FIXME: use the getname()/SFDReadUTF7Str() combo
	    // from SFDGetChar
	    int len = strlen("StartChar:");
	    while( line[len] && line[len] == ' ' )
		len++;
	    strcpy( ret, line+len );
	    free(line);
	    return copy(ret);
	}
	free(line);
	if(feof( sfd ))
	    break;

    }
    return 0;
}


void SFDFixupRefs(SplineFont *sf) {
    int i, isv;
    RefChar *refs, *rnext, *rprev;
    /*int isautorecovery = sf->changed;*/
    KernPair *kp, *prev, *next;
    EncMap *map = sf->map;
    int layer;
    int k,l;
    SplineFont *cidmaster = sf, *ksf;

    k = 1;
    if ( sf->subfontcnt!=0 )
	sf = sf->subfonts[0];

    ff_progress_change_line2(_("Interpreting Glyphs"));
    for (;;) {
	for ( i=0; i<sf->glyphcnt; ++i ) if ( sf->glyphs[i]!=NULL ) {
	    SplineChar *sc = sf->glyphs[i];
	    /* A changed character is one that has just been recovered */
	    /*  unchanged characters will already have been fixed up */
	    /* Er... maybe not. If the character being recovered is refered to */
	    /*  by another character then we need to fix up that other char too*/
	    /*if ( isautorecovery && !sc->changed )*/
	/*continue;*/
	    for ( layer = 0; layer<sc->layer_cnt; ++layer ) {
		rprev = NULL;
		for ( refs = sc->layers[layer].refs; refs!=NULL; refs=rnext ) {
		    rnext = refs->next;
		    if ( refs->encoded ) {		/* Old sfd format */
			if ( refs->orig_pos<map->encmax && map->map[refs->orig_pos]!=-1 )
			    refs->orig_pos = map->map[refs->orig_pos];
			else
			    refs->orig_pos = sf->glyphcnt;
			refs->encoded = false;
		    }
		    if ( refs->orig_pos<sf->glyphcnt && refs->orig_pos>=0 )
			refs->sc = sf->glyphs[refs->orig_pos];
		    if ( refs->sc!=NULL ) {
			refs->unicode_enc = refs->sc->unicodeenc;
			refs->adobe_enc = getAdobeEnc(refs->sc->name);
			rprev = refs;
			if ( refs->use_my_metrics ) {
			    if ( sc->width != refs->sc->width ) {
				LogError(_("Bad sfd file. Glyph %s has width %d even though it should be\n  bound to the width of %s which is %d.\n"),
					sc->name, sc->width, refs->sc->name, refs->sc->width );
				sc->width = refs->sc->width;
			    }
			}
		    } else {
			RefCharFree(refs);
			if ( rprev!=NULL )
			    rprev->next = rnext;
			else
			    sc->layers[layer].refs = rnext;
		    }
		}
	    }
	    /* In old sfd files we used a peculiar idiom to represent a multiply */
	    /*  encoded glyph. Fix it up now. Remove the fake glyph and adjust the*/
	    /*  map */
	    /*if ( isautorecovery && !sc->changed )*/
	/*continue;*/
	    for ( isv=0; isv<2; ++isv ) {
		for ( prev = NULL, kp=isv?sc->vkerns : sc->kerns; kp!=NULL; kp=next ) {
		    int index = (intpt) (kp->sc);

		    next = kp->next;
		    // be impotent if the reference is already to the correct location
                    if ( !kp->kcid ) {	/* It's encoded (old sfds), else orig */
                        if ( index>=map->encmax || map->map[index]==-1 )
                            index = sf->glyphcnt;
                        else
                            index = map->map[index];
                    }
                    kp->kcid = false;
                    ksf = sf;
                    if ( cidmaster!=sf ) {
                        for ( l=0; l<cidmaster->subfontcnt; ++l ) {
                            ksf = cidmaster->subfonts[l];
                            if ( index<ksf->glyphcnt && ksf->glyphs[index]!=NULL )
                                break;
                        }
                    }
                    if ( index>=ksf->glyphcnt || ksf->glyphs[index]==NULL ) {
                        IError( "Bad kerning information in glyph %s\n", sc->name );
                        kp->sc = NULL;
                    } else {
                        kp->sc = ksf->glyphs[index];
                    }

		    if ( kp->sc!=NULL )
			prev = kp;
		    else{
			if ( prev!=NULL )
			    prev->next = next;
			else if ( isv )
			    sc->vkerns = next;
			else
			    sc->kerns = next;
			chunkfree(kp,sizeof(KernPair));
		    }
		}
	    }
	    if ( SCDuplicate(sc)!=sc ) {
		SplineChar *base = SCDuplicate(sc);
		int orig = sc->orig_pos, enc = sf->map->backmap[orig], uni = sc->unicodeenc;
		SplineCharFree(sc);
		sf->glyphs[i]=NULL;
		sf->map->backmap[orig] = -1;
		sf->map->map[enc] = base->orig_pos;
		AltUniAdd(base,uni);
	    }
	}
	for ( i=0; i<sf->glyphcnt; ++i ) if ( sf->glyphs[i]!=NULL ) {
	    SplineChar *sc = sf->glyphs[i];
	    for ( layer=0; layer<sc->layer_cnt; ++layer ) {
		for ( refs = sf->glyphs[i]->layers[layer].refs; refs!=NULL; refs=refs->next ) {
		    SFDFixupRef(sf->glyphs[i],refs,layer);
		}
	    }
	    ff_progress_next();
	}
	if ( sf->cidmaster==NULL )
	    for ( i=sf->glyphcnt-1; i>=0 && sf->glyphs[i]==NULL; --i )
		sf->glyphcnt = i;
	if ( k>=cidmaster->subfontcnt )
    break;
	sf = cidmaster->subfonts[k++];
    }
}


static uint32 gettag(FILE *sfd) {
    int ch, quoted;
    uint32 tag;

    while ( (ch=nlgetc(sfd))==' ' );
    if ( (quoted = (ch=='\'')) ) ch = nlgetc(sfd);
    tag = (ch<<24)|(nlgetc(sfd)<<16);
    tag |= nlgetc(sfd)<<8;
    tag |= nlgetc(sfd);
    if ( quoted ) (void) nlgetc(sfd);
return( tag );
}


static void SFD_DoAltUnis(SplineFont *sf) {
    int i;
    struct altuni *alt;
    SplineChar *sc;

    for ( i=0; i<sf->glyphcnt; ++i ) if ( (sc = sf->glyphs[i])!=NULL ) {
	for ( alt = sc->altuni; alt!=NULL; alt = alt->next ) {
	    if ( alt->vs==-1 && alt->fid==0 ) {
		int enc = EncFromUni(alt->unienc,sf->map->enc);
		if ( enc!=-1 )
		    SFDSetEncMap(sf,sc->orig_pos,enc);
	    }
	}
    }
}


static void ACDisassociateLigatures(SplineFont1 *sf,AnchorClass1 *ac) {
    int gid,k;
    SplineFont1 *subsf;
    SplineChar *sc;
    AnchorPoint *ap, *lap;
    AnchorClass1 *lac;
    char *format;

    lac = chunkalloc(sizeof(AnchorClass1));
    *lac = *ac;
    lac->ac.type = act_mklg;
    ac->ac.next = (AnchorClass *) lac;

  /* GT: Need to split some AnchorClasses into two classes, one for normal */
  /* GT:  base letters, and one for ligatures. So create a new AnchorClass */
  /* GT:  name for the ligature version */
    format = _("Ligature %s");
    lac->ac.name = malloc(strlen(ac->ac.name)+strlen(format)+1);
    sprintf( lac->ac.name, format, ac->ac.name );

    k=0;
    do {
	subsf = sf->sf.subfontcnt==0 ? sf : (SplineFont1 *) (sf->sf.subfonts[k]);
	for ( gid=0; gid<subsf->sf.glyphcnt; ++gid ) if ( (sc=subsf->sf.glyphs[gid])!=NULL ) {
	    for ( ap=sc->anchor; ap!=NULL; ap=ap->next ) {
		if ( ap->anchor!=(AnchorClass *) ac )
	    continue;
		if ( ap->type==at_mark ) {
		    lap = chunkalloc(sizeof(AnchorPoint));
		    *lap = *ap;
		    ap->next = lap;
		    lap->anchor = (AnchorClass *) lac;
		} else if ( ap->type==at_baselig ) {
		    ap->anchor = (AnchorClass *) lac;
		}
	    }
	}
	++k;
    } while ( k<sf->sf.subfontcnt );
}


static void SFDDumpDHintList( FILE *sfd,const char *key, DStemInfo *d ) {
    HintInstance *hi;

    if ( d==NULL )
return;
    fprintf(sfd, "%s", key );
    for ( ; d!=NULL; d=d->next ) {
	fprintf(sfd, "%g %g %g %g %g %g",
		(double) d->left.x, (double) d->left.y,
		(double) d->right.x, (double) d->right.y,
		(double) d->unit.x, (double) d->unit.y );
	if ( d->where!=NULL ) {
	    putc('<',sfd);
	    for ( hi=d->where; hi!=NULL; hi=hi->next )
		fprintf(sfd, "%g %g%c", (double) hi->begin, (double) hi->end, hi->next?' ':'>');
	}
	putc(d->next?' ':'\n',sfd);
    }
}


static int SFDGetBitmapReference(FILE *sfd,BDFFont *bdf) {
    BDFChar *bc;
    BDFRefChar *ref, *head;
    int gid, rgid, xoff, yoff;
    char ch;

    /* 'BDFRefChar:' elements should not occur in the file before the corresponding */
    /* 'BDFChar:'. However it is possible that the glyphs they refer to are not yet */
    /* available. So we will find them later */
    if ( getint(sfd,&gid)!=1 || gid<=0 || gid >= bdf->glyphcnt || ( bc = bdf->glyphs[gid] ) == NULL )
return( 0 );
    if ( getint(sfd,&rgid)!=1 || rgid<0 )
return( 0 );
    if ( getint(sfd,&xoff)!=1 )
return( 0 );
    if ( getint(sfd,&yoff)!=1 )
return( 0 );
    while ( isspace( ch=nlgetc( sfd )) && ch!='\r' && ch!='\n' );

    ref = calloc( 1,sizeof( BDFRefChar ));
    ref->gid = rgid; ref->xoff = xoff, ref->yoff = yoff;
    if ( ch == 'S' ) ref->selected = true;
    for ( head = bc->refs; head != NULL && head->next!=NULL; head = head->next );
    if ( head == NULL ) bc->refs = ref;
    else head->next = ref;
return( 1 );
}


static int SFAddScriptLangIndex(SplineFont *_sf,uint32 script,uint32 lang) {
    int i;
    SplineFont1 *sf;

    if ( _sf->cidmaster ) _sf = _sf->cidmaster;
    else if ( _sf->mm!=NULL ) _sf=_sf->mm->normal;

    if ( _sf->sfd_version>=2 )
	IError( "SFFindBiggestScriptLangIndex called with bad version number.\n" );

    sf = (SplineFont1 *) _sf;

    if ( script==0 ) script=DEFAULT_SCRIPT;
    if ( lang==0 ) lang=DEFAULT_LANG;
    if ( sf->script_lang==NULL )
	sf->script_lang = calloc(2,sizeof(struct script_record *));
    for ( i=0; sf->script_lang[i]!=NULL; ++i ) {
	if ( sf->script_lang[i][0].script==script && sf->script_lang[i][1].script==0 &&
		sf->script_lang[i][0].langs[0]==lang &&
		sf->script_lang[i][0].langs[1]==0 )
return( i );
    }
    sf->script_lang = realloc(sf->script_lang,(i+2)*sizeof(struct script_record *));
    sf->script_lang[i] = calloc(2,sizeof(struct script_record));
    sf->script_lang[i][0].script = script;
    sf->script_lang[i][0].langs = malloc(2*sizeof(uint32));
    sf->script_lang[i][0].langs[0] = lang;
    sf->script_lang[i][0].langs[1] = 0;
    sf->script_lang[i+1] = NULL;
    sf->sli_cnt = i+1;
return( i );
}


static FeatureScriptLangList *FeaturesFromTagSli(uint32 tag,int sli,SplineFont1 *sf) {
    FeatureScriptLangList *fl;
    struct script_record *sr;
    struct scriptlanglist *cur, *last;
    int i;

    fl = chunkalloc(sizeof(FeatureScriptLangList));
    fl->featuretag = tag;
    if ( sli==SLI_NESTED || sli<0 || sli>=sf->sli_cnt )
return( fl );
    last = NULL;
    for ( sr = sf->script_lang[sli]; sr->script!=0; ++sr ) {
	cur = chunkalloc(sizeof(struct scriptlanglist));
	cur->script = sr->script;
	for ( i=0; sr->langs[i]!=0; ++i );
	cur->lang_cnt = i;
	if ( i>MAX_LANG )
	    cur->morelangs = malloc((i-MAX_LANG) * sizeof(uint32));
	for ( i=0; sr->langs[i]!=0; ++i ) {
	    if ( i<MAX_LANG )
		cur->langs[i] = sr->langs[i];
	    else
		cur->morelangs[i-MAX_LANG] = sr->langs[i];
	}
	if ( last==NULL )
	    fl->scripts = cur;
	else
	    last->next = cur;
	last = cur;
    }
return( fl );
}


static void SFFinalDirClean(char *filename) {
    DIR *dir;
    struct dirent *ent;
    char *buffer, *markerfile, *pt;

    /* we did not unlink sub-directories in case they contained version control */
    /*  files. We did remove all our files from them, however.  If the user */
    /*  removed a bitmap strike or a cid-subfont those sub-dirs will now be */
    /*  empty. If the user didn't remove them then they will contain our marker */
    /*  files. So if we find a subdir with no marker files in it, remove it */
    dir = opendir(filename);
    if ( dir==NULL )
return;
    buffer = malloc(strlen(filename)+1+NAME_MAX+1);
    markerfile = malloc(strlen(filename)+2+2*NAME_MAX+1);
    while ( (ent = readdir(dir))!=NULL ) {
	if ( strcmp(ent->d_name,".")==0 || strcmp(ent->d_name,"..")==0 )
    continue;
	pt = strrchr(ent->d_name,EXT_CHAR);
	if ( pt==NULL )
    continue;
	sprintf( buffer,"%s/%s", filename, ent->d_name );
	if ( strcmp(pt,".strike")==0 ||
		strcmp(pt,SUBFONT_EXT)==0 ||
		strcmp(pt,INSTANCE_EXT)==0 ) {
	    if ( strcmp(pt,".strike")==0 )
		sprintf( markerfile,"%s/" STRIKE_PROPS, buffer );
	    else
		sprintf( markerfile,"%s/" FONT_PROPS, buffer );
	    if ( !GFileExists(markerfile)) {
		GFileRemove(buffer, false);
	    }
	}
    }
    free(buffer);
    free(markerfile);
    closedir(dir);
}

# define NAME_MAX _POSIX_NAME_MAX

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };

#define FONT_PROPS	"font.props"

#define STRIKE_PROPS	"strike.props"

#define EXT_CHAR	'.'

#define BITMAP_EXT	".bitmap"

#define STRIKE_EXT	".strike"

#define SUBFONT_EXT	".subfont"

#define INSTANCE_EXT	".instance"


static struct ttflangname *SFDGetLangName(FILE *sfd,struct ttflangname *old) {
    struct ttflangname *cur = chunkalloc(sizeof(struct ttflangname)), *prev;
    int i;

    getint(sfd,&cur->lang);
    for ( i=0; i<ttf_namemax; ++i )
	cur->names[i] = SFDReadUTF7Str(sfd);
    if ( old==NULL )
return( cur );
    for ( prev = old; prev->next !=NULL; prev = prev->next );
    prev->next = cur;
return( old );
}


static ValDevTab *SFDReadValDevTab(FILE *sfd) {
    int i, j, ch;
    ValDevTab vdt;
    char buf[4];

    memset(&vdt,0,sizeof(vdt));
    buf[3] = '\0';
    while ( (ch=nlgetc(sfd))==' ' );
    if ( ch=='[' ) {
	for ( i=0; i<4; ++i ) {
	    while ( (ch=nlgetc(sfd))==' ' );
	    if ( ch==']' )
	break;
	    buf[0]=ch;
	    for ( j=1; j<3; ++j ) buf[j]=nlgetc(sfd);
	    while ( (ch=nlgetc(sfd))==' ' );
	    if ( ch!='=' ) ungetc(ch,sfd);
	    SFDReadDeviceTable(sfd,
		    strcmp(buf,"ddx")==0 ? &vdt.xadjust :
		    strcmp(buf,"ddy")==0 ? &vdt.yadjust :
		    strcmp(buf,"ddh")==0 ? &vdt.xadv :
		    strcmp(buf,"ddv")==0 ? &vdt.yadv :
			(&vdt.xadjust) + i );
	    while ( (ch=nlgetc(sfd))==' ' );
	    if ( ch!=']' ) ungetc(ch,sfd);
	    else
	break;
	}
	if ( vdt.xadjust.corrections!=NULL || vdt.yadjust.corrections!=NULL ||
		vdt.xadv.corrections!=NULL || vdt.yadv.corrections!=NULL ) {
	    ValDevTab *v = chunkalloc(sizeof(ValDevTab));
	    *v = vdt;
return( v );
	}
    } else
	ungetc(ch,sfd);
return( NULL );
}


static void SFDGetType1(FILE *sfd) {
    /* We've read the OrigType1 token (this is now obselete, but parse it in case there are any old sfds) */
    int len;
    struct enc85 dec;

    memset(&dec,'\0', sizeof(dec)); dec.pos = -1;
    dec.sfd = sfd;

    getint(sfd,&len);
    while ( --len >= 0 )
	Dec85(&dec);
}


static void SFDDumpSplineSet(FILE *sfd, SplineSet *spl, int want_order2) {
    SplinePoint *first, *sp;
    // If there's no spline structure there should just be a single point,
    // which is compatible with either order and therefore want_order2
    int order2 = spl->first->next!=NULL ? spl->first->next->order2 : want_order2;
    int reduce = (want_order2 && !order2);
    if (order2 && !want_order2)
	IError("Asked for cubic when had quadratic");
    SplineSet *nspl;

    for ( ; spl!=NULL; spl=spl->next ) {
	if (reduce) {
	    nspl = SSttfApprox(spl);
	    order2 = true;
	} else {
	    nspl = spl;
	}
	first = NULL;
	for ( sp = nspl->first; ; sp=sp->next->to ) {
#ifndef FONTFORGE_CONFIG_USE_DOUBLE
	    if ( first==NULL )
		fprintf( sfd, "%g %g m ", (double) sp->me.x, (double) sp->me.y );
	    else if ( sp->prev->islinear && sp->noprevcp )		/* Don't use known linear here. save control points if there are any */
		fprintf( sfd, " %g %g l ", (double) sp->me.x, (double) sp->me.y );
	    else
		fprintf( sfd, " %g %g %g %g %g %g c ",
			(double) sp->prev->from->nextcp.x, (double) sp->prev->from->nextcp.y,
			(double) sp->prevcp.x, (double) sp->prevcp.y,
			(double) sp->me.x, (double) sp->me.y );
#else
	    if ( first==NULL )
		fprintf( sfd, "%.12g %.12g m ", (double) sp->me.x, (double) sp->me.y );
	    else if ( sp->prev->islinear && sp->noprevcp )		/* Don't use known linear here. save control points if there are any */
		fprintf( sfd, " %.12g %.12g l ", (double) sp->me.x, (double) sp->me.y );
	    else
		fprintf( sfd, " %.12g %.12g %.12g %.12g %.12g %.12g c ",
			(double) sp->prev->from->nextcp.x, (double) sp->prev->from->nextcp.y,
			(double) sp->prevcp.x, (double) sp->prevcp.y,
			(double) sp->me.x, (double) sp->me.y );
#endif
	    int ptflags = 0;
	    ptflags = sp->pointtype|(sp->selected<<2)|
		(sp->nextcpdef<<3)|(sp->prevcpdef<<4)|
		(sp->roundx<<5)|(sp->roundy<<6)|
		(sp->ttfindex==0xffff?(1<<7):0)|
		(sp->dontinterpolate<<8)|
		((sp->prev && sp->prev->acceptableextrema)<<9);

	    // Last point in the splineset, and we are an open path.
	    if( !sp->next
		&& spl->first && !spl->first->prev )
	    {
		ptflags |= SFD_PTFLAG_FORCE_OPEN_PATH;
	    }

	    fprintf(sfd, "%d", ptflags );
	    if ( order2 ) {
		if ( sp->ttfindex!=0xfffe && sp->nextcpindex!=0xfffe ) {
		    putc(',',sfd);
		    if ( sp->ttfindex==0xffff )
			fprintf(sfd,"-1");
		    else if ( sp->ttfindex!=0xfffe )
			fprintf(sfd,"%d",sp->ttfindex);
		    if ( sp->nextcpindex==0xffff )
			fprintf(sfd,",-1");
		    else if ( sp->nextcpindex!=0xfffe )
			fprintf(sfd,",%d",sp->nextcpindex);
		}
	    } else {
		if ( sp->hintmask!=NULL ) {
		    putc('x',sfd);
		    SFDDumpHintMask(sfd, sp->hintmask);
		}
	    }
	    putc('\n',sfd);
	    if (sp->name != NULL) {
		fputs("NamedP: ", sfd);
		SFDDumpUTF7Str(sfd, sp->name);
		putc('\n', sfd);
	    }
	    if ( sp==first )
	break;
	    if ( first==NULL ) first = sp;
	    if ( sp->next==NULL )
	break;
	}
	if ( spl->spiro_cnt!=0 ) {
	    int i;
	    fprintf( sfd, "  Spiro\n" );
	    for ( i=0; i<spl->spiro_cnt; ++i ) {
		fprintf( sfd, "    %g %g %c\n", spl->spiros[i].x, spl->spiros[i].y,
			    spl->spiros[i].ty&0x7f);
	    }
	    fprintf( sfd, "  EndSpiro\n" );
	}
	if ( spl->contour_name!=NULL ) {
	    fprintf( sfd, "  Named: " );
	    SFDDumpUTF7Str(sfd,spl->contour_name);
	    putc('\n',sfd);
	}
	if ( spl->is_clip_path ) {
	    fprintf( sfd, "  PathFlags: %d\n", spl->is_clip_path );
	}
	if ( spl->start_offset ) {
	    fprintf( sfd, "  PathStart: %d\n", spl->start_offset );
	}
    if (reduce) SplinePointListFree(nspl);
    }
    fprintf( sfd, "EndSplineSet\n" );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

#define SFD_PTFLAG_FORCE_OPEN_PATH    0x400

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };

static char base64[64] = {
 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

static const char *end_tt_instrs = "EndTTInstrs";

static void SFDConsumeUntil( FILE *sfd, const char** terminators );

static void SFDDumpRefs(FILE *sfd,RefChar *refs, int *newgids);

static void SFDDumpGuidelines(FILE *sfd,GuidelineSet *gl);

static RefChar *SFDGetRef(FILE *sfd, int was_enc);

static void SFDDumpImage(FILE *sfd,ImageList *img);

static void SFDDumpImagePNG(FILE *sfd,ImageList *img);

static AnchorPoint *SFDReadAnchorPoints(FILE *sfd,SplineChar *sc,AnchorPoint** alist, AnchorPoint *lastap);


static int getusint(FILE *sfd, uint16 *val) {
    int val2;
    int ret = getint(sfd,&val2);
    *val = val2;
return( ret );
}


static StemInfo *SFDReadHints(FILE *sfd) {
    StemInfo *head=NULL, *last=NULL, *cur;
    real start, width;

    while ( getreal(sfd,&start)==1 && getreal(sfd,&width)) {
	cur = chunkalloc(sizeof(StemInfo));
	cur->start = start;
	cur->width = width;
	cur->where = SFDReadHintInstances(sfd,cur);
	if ( head == NULL )
	    head = cur;
	else
	    last->next = cur;
	last = cur;
    }
return( head );
}

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };

static char base64[64] = {
 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

static const char *end_tt_instrs = "EndTTInstrs";

static void SFDConsumeUntil( FILE *sfd, const char** terminators );

static void SFDDumpRefs(FILE *sfd,RefChar *refs, int *newgids);

static void SFDDumpGuidelines(FILE *sfd,GuidelineSet *gl);

static RefChar *SFDGetRef(FILE *sfd, int was_enc);

static void SFDDumpImage(FILE *sfd,ImageList *img);

static void SFDDumpImagePNG(FILE *sfd,ImageList *img);

static AnchorPoint *SFDReadAnchorPoints(FILE *sfd,SplineChar *sc,AnchorPoint** alist, AnchorPoint *lastap);

static GuidelineSet *SFDReadGuideline(FILE *sfd, GuidelineSet **gll, GuidelineSet *lastgl);

static void SFDDumpTtfInstrs(FILE *sfd,SplineChar *sc);


static void CvtOldMacFeature(PST1 *pst) {
    int i;

    if ( pst->macfeature )
return;
    for ( i=0; formertags[i].feature!=-1 ; ++i ) {
	if ( pst->tag == formertags[i].tag ) {
	    pst->macfeature = true;
	    pst->tag = (formertags[i].feature<<16) | formertags[i].setting;
return;
	}
    }
}


void SFTimesFromFile(SplineFont *sf,FILE *file) {
    struct stat b;
    if ( fstat(fileno(file),&b)!=-1 ) {
	sf->modificationtime = GetST_MTime(b);
	sf->creationtime = GetST_MTime(b);
    }
}

static void SFDConsumeUntil( FILE *sfd, const char** terminators );

static void SFDDumpRefs(FILE *sfd,RefChar *refs, int *newgids);

static void SFDDumpGuidelines(FILE *sfd,GuidelineSet *gl);

static RefChar *SFDGetRef(FILE *sfd, int was_enc);

static void SFDDumpImage(FILE *sfd,ImageList *img);

static void SFDDumpImagePNG(FILE *sfd,ImageList *img);

static AnchorPoint *SFDReadAnchorPoints(FILE *sfd,SplineChar *sc,AnchorPoint** alist, AnchorPoint *lastap);

static GuidelineSet *SFDReadGuideline(FILE *sfd, GuidelineSet **gll, GuidelineSet *lastgl);

static void SFDDumpTtfInstrs(FILE *sfd,SplineChar *sc);

static void SFDDumpTtfInstrsExplicit(FILE *sfd,uint8 *ttf_instrs, int16 ttf_instrs_len );

static void SFDDumpHintList(FILE *sfd,const char *key, StemInfo *h);

static void SFDDumpDHintList( FILE *sfd,const char *key, DStemInfo *d );

static StemInfo *SFDReadHints(FILE *sfd);

static DStemInfo *SFDReadDHints( SplineFont *sf,FILE *sfd,int old );

struct enc85 {
    FILE *sfd;
    unsigned char sofar[4];
    int pos;
    int ccnt;
}

static struct { int feature, setting; uint32 tag; } formertags[] = {
    { 1, 6, CHR('M','L','O','G') },
    { 1, 8, CHR('M','R','E','B') },
    { 1, 10, CHR('M','D','L','G') },
    { 1, 12, CHR('M','S','L','G') },
    { 1, 14, CHR('M','A','L','G') },
    { 8, 0, CHR('M','S','W','I') },
    { 8, 2, CHR('M','S','W','F') },
    { 8, 4, CHR('M','S','L','I') },
    { 8, 6, CHR('M','S','L','F') },
    { 8, 8, CHR('M','S','N','F') },
    { 22, 1, CHR('M','W','I','D') },
    { 27, 1, CHR('M','U','C','M') },
    { 103, 2, CHR('M','W','I','D') },
    { -1, -1, 0xffffffff },
};

static SplineFont *SFD_GetFont(FILE *sfd,SplineFont *cidmaster,char *tok,
	int fromdir, char *dirname, float sfdversion);


static AnchorPoint *SFDReadAnchorPoints(FILE *sfd,SplineChar *sc,AnchorPoint** alist, AnchorPoint *lastap)
{
    AnchorPoint *ap = chunkalloc(sizeof(AnchorPoint));
    AnchorClass *an;
    char *name;
    char tok[200];
    int ch;

    name = SFDReadUTF7Str(sfd);
    if ( name==NULL ) {
	LogError(_("Anchor Point with no class name: %s"), sc->name );
	AnchorPointsFree(ap);
return( lastap );
    }
    for ( an=sc->parent->anchor; an!=NULL && strcmp(an->name,name)!=0; an=an->next );
    free(name);
    ap->anchor = an;
    getreal(sfd,&ap->me.x);
    getreal(sfd,&ap->me.y);
    ap->type = -1;
    if ( getname(sfd,tok)==1 ) {
	if ( strcmp(tok,"mark")==0 )
	    ap->type = at_mark;
	else if ( strcmp(tok,"basechar")==0 )
	    ap->type = at_basechar;
	else if ( strcmp(tok,"baselig")==0 )
	    ap->type = at_baselig;
	else if ( strcmp(tok,"basemark")==0 )
	    ap->type = at_basemark;
	else if ( strcmp(tok,"entry")==0 )
	    ap->type = at_centry;
	else if ( strcmp(tok,"exit")==0 )
	    ap->type = at_cexit;
    }
    getsint(sfd,&ap->lig_index);
    ch = nlgetc(sfd);
    ungetc(ch,sfd);
    if ( ch==' ' ) {
	SFDReadDeviceTable(sfd,&ap->xadjust);
	SFDReadDeviceTable(sfd,&ap->yadjust);
	ch = nlgetc(sfd);
	ungetc(ch,sfd);
	if ( isdigit(ch)) {
	    getsint(sfd,(int16 *) &ap->ttf_pt_index);
	    ap->has_ttf_pt = true;
	}
    }
    if ( ap->anchor==NULL || ap->type==-1 ) {
	LogError(_("Bad Anchor Point: %s"), sc->name );
	AnchorPointsFree(ap);
return( lastap );
    }
    if ( lastap==NULL )
	(*alist) = ap;
    else
	lastap->next = ap;

    return( ap );
}


static char *SFDReadUTF7Str(FILE *sfd) {
    char *buffer = NULL, *pt, *end = NULL;
    int ch1, ch2, ch3, ch4, done, c;
    int prev_cnt=0, prev=0, in=0;

    ch1 = nlgetc(sfd);
    while ( isspace(ch1) && ch1!='\n' && ch1!='\r') ch1 = nlgetc(sfd);
    if ( ch1=='\n' || ch1=='\r' )
	ungetc(ch1,sfd);
    if ( ch1!='"' )
return( NULL );
    pt = 0;
    while ( (ch1=nlgetc(sfd))!=EOF && ch1!='"' ) {
	done = 0;
	if ( !done && !in ) {
	    if ( ch1=='+' ) {
		ch1 = nlgetc(sfd);
		if ( ch1=='-' ) {
		    ch1 = '+';
		    done = true;
		} else {
		    in = true;
		    prev_cnt = 0;
		}
	    } else
		done = true;
	}
	if ( !done ) {
	    if ( ch1=='-' ) {
		in = false;
	    } else if ( inbase64[ch1]==-1 ) {
		in = false;
		done = true;
	    } else {
		ch1 = inbase64[ch1];
		ch2 = inbase64[c = nlgetc(sfd)];
		if ( ch2==-1 ) {
		    ungetc(c, sfd);
		    ch2 = ch3 = ch4 = 0;
		} else {
		    ch3 = inbase64[c = nlgetc(sfd)];
		    if ( ch3==-1 ) {
			ungetc(c, sfd);
			ch3 = ch4 = 0;
		    } else {
			ch4 = inbase64[c = nlgetc(sfd)];
			if ( ch4==-1 ) {
			    ungetc(c, sfd);
			    ch4 = 0;
			}
		    }
		}
		ch1 = (ch1<<18) | (ch2<<12) | (ch3<<6) | ch4;
		if ( prev_cnt==0 ) {
		    prev = ch1&0xff;
		    ch1 >>= 8;
		    prev_cnt = 1;
		} else /* if ( prev_cnt == 1 ) */ {
		    ch1 |= (prev<<24);
		    prev = (ch1&0xffff);
		    ch1 = (ch1>>16)&0xffff;
		    prev_cnt = 2;
		}
		done = true;
	    }
	}
	if ( pt+10>=end ) {
	    if ( buffer==NULL ) {
		pt = buffer = malloc(400);
		end = buffer+400;
	    } else if (pt) {
		char *temp = realloc(buffer,end-buffer+400);
		pt = temp+(pt-buffer);
		end = temp+(end-buffer+400);
		buffer = temp;
	    }
	}
	if ( pt && done )
	    pt = utf8_idpb(pt,ch1,0);
	if ( prev_cnt==2 ) {
	    prev_cnt = 0;
	    if ( pt && prev!=0 )
		pt = utf8_idpb(pt,prev,0);
	}
	if ( pt==0 ) {
	    free(buffer);
	    return( NULL );
	}
    }
    if ( buffer==NULL )
return( NULL );
    *pt = '\0';
    pt = copy(buffer);
    free(buffer );
return( pt );
}


FILE* MakeTemporaryFile(void) {
    FILE *ret = NULL;

#ifndef __MINGW32__
    gchar *loc;
    int fd = g_file_open_tmp("fontforge-XXXXXX", &loc, NULL);

    if (fd != -1) {
        ret = fdopen(fd, "w+");
        g_unlink(loc);
        g_free(loc);
    }
#else
    HANDLE hFile = INVALID_HANDLE_VALUE;
    for (int retries = 0; hFile == INVALID_HANDLE_VALUE && retries < 10; retries++) {
        wchar_t *temp = _wtempnam(NULL, L"FontForge");
        hFile = CreateFileW(temp, GENERIC_READ|GENERIC_WRITE, 0, NULL,
            CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, NULL);
        free(temp);
    }
    ret = _fdopen(_open_osfhandle((intptr_t)hFile, 0), "wb+");
    if (ret == NULL && hFile != INVALID_HANDLE_VALUE) {
        CloseHandle(hFile);
    }
#endif
    return ret;
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };

static void SFDConsumeUntil( FILE *sfd, const char** terminators );

static void SFDDumpRefs(FILE *sfd,RefChar *refs, int *newgids);

static void SFDDumpGuidelines(FILE *sfd,GuidelineSet *gl);

static RefChar *SFDGetRef(FILE *sfd, int was_enc);

static void SFDDumpImage(FILE *sfd,ImageList *img);

static void SFDDumpImagePNG(FILE *sfd,ImageList *img);

static AnchorPoint *SFDReadAnchorPoints(FILE *sfd,SplineChar *sc,AnchorPoint** alist, AnchorPoint *lastap);

static GuidelineSet *SFDReadGuideline(FILE *sfd, GuidelineSet **gll, GuidelineSet *lastgl);

static void SFDDumpTtfInstrs(FILE *sfd,SplineChar *sc);

static void SFDDumpTtfInstrsExplicit(FILE *sfd,uint8 *ttf_instrs, int16 ttf_instrs_len );

static void SFDDumpHintList(FILE *sfd,const char *key, StemInfo *h);

static void SFDDumpDHintList( FILE *sfd,const char *key, DStemInfo *d );

static StemInfo *SFDReadHints(FILE *sfd);

static DStemInfo *SFDReadDHints( SplineFont *sf,FILE *sfd,int old );

struct enc85 {
    FILE *sfd;
    unsigned char sofar[4];
    int pos;
    int ccnt;
}


static int SFD_MMDump(FILE *sfd,SplineFont *sf,EncMap *map,EncMap *normal,
	int todir, char *dirname) {
    MMSet *mm = sf->mm;
    int max, i, j;
    int err = false;

    fprintf( sfd, "MMCounts: %d %d %d %d\n", mm->instance_count, mm->axis_count,
	    mm->apple, mm->named_instance_count );
    fprintf( sfd, "MMAxis:" );
    for ( i=0; i<mm->axis_count; ++i )
	fprintf( sfd, " %s", mm->axes[i]);
    putc('\n',sfd);
    fprintf( sfd, "MMPositions:" );
    for ( i=0; i<mm->axis_count*mm->instance_count; ++i )
	fprintf( sfd, " %g", (double) mm->positions[i]);
    putc('\n',sfd);
    fprintf( sfd, "MMWeights:" );
    for ( i=0; i<mm->instance_count; ++i )
	fprintf( sfd, " %g", (double) mm->defweights[i]);
    putc('\n',sfd);
    for ( i=0; i<mm->axis_count; ++i ) {
	fprintf( sfd, "MMAxisMap: %d %d", i, mm->axismaps[i].points );
	for ( j=0; j<mm->axismaps[i].points; ++j )
	    fprintf( sfd, " %g=>%g", (double) mm->axismaps[i].blends[j], (double) mm->axismaps[i].designs[j]);
	fputc('\n',sfd);
	SFDDumpMacName(sfd,mm->axismaps[i].axisnames);
    }
    if ( mm->cdv!=NULL ) {
	fprintf( sfd, "MMCDV:\n" );
	fputs(mm->cdv,sfd);
	fprintf( sfd, "\nEndMMSubroutine\n" );
    }
    if ( mm->ndv!=NULL ) {
	fprintf( sfd, "MMNDV:\n" );
	fputs(mm->ndv,sfd);
	fprintf( sfd, "\nEndMMSubroutine\n" );
    }
    for ( i=0; i<mm->named_instance_count; ++i ) {
	fprintf( sfd, "MMNamedInstance: %d ", i );
	for ( j=0; j<mm->axis_count; ++j )
	    fprintf( sfd, " %g", (double) mm->named_instances[i].coords[j]);
	fputc('\n',sfd);
	SFDDumpMacName(sfd,mm->named_instances[i].names);
    }

    if ( todir ) {
	for ( i=0; i<mm->instance_count; ++i )
	    err |= SFD_MIDump(mm->instances[i],map,dirname,i+1);
	err |= SFD_MIDump(mm->normal,map,dirname,0);
    } else {
	for ( i=max=0; i<mm->instance_count; ++i )
	    if ( max<mm->instances[i]->glyphcnt )
		max = mm->instances[i]->glyphcnt;
	fprintf(sfd, "BeginMMFonts: %d %d\n", mm->instance_count+1, max );
	for ( i=0; i<mm->instance_count; ++i )
	    SFD_Dump(sfd,mm->instances[i],map,normal,todir,dirname);
	SFD_Dump(sfd,mm->normal,map,normal,todir,dirname);
    }
    fprintf(sfd, "EndMMFonts\n" );
return( err );
}


static int Dec85(struct enc85 *dec) {
    int ch1, ch2, ch3, ch4, ch5;
    unsigned int val;

    if ( dec->pos<0 ) {
	while ( isspace(ch1=getc(dec->sfd)));
	if ( ch1=='z' ) {
	    dec->sofar[0] = dec->sofar[1] = dec->sofar[2] = dec->sofar[3] = 0;
	    dec->pos = 3;
	} else {
	    while ( isspace(ch2=getc(dec->sfd)));
	    while ( isspace(ch3=getc(dec->sfd)));
	    while ( isspace(ch4=getc(dec->sfd)));
	    while ( isspace(ch5=getc(dec->sfd)));
	    val = ((((ch1-'!')*85+ ch2-'!')*85 + ch3-'!')*85 + ch4-'!')*85 + ch5-'!';
	    dec->sofar[3] = val>>24;
	    dec->sofar[2] = val>>16;
	    dec->sofar[1] = val>>8;
	    dec->sofar[0] = val;
	    dec->pos = 3;
	}
    }
return( dec->sofar[dec->pos--] );
}


static void SFDFixupBitmapRefs( BDFFont *bdf ) {
    BDFChar *bc, *rbc;
    BDFRefChar *head, *next, *prev;
    int i;

    for ( i=0; i<bdf->glyphcnt; i++ ) if (( bc = bdf->glyphs[i] ) != NULL ) {
	prev = NULL;
	for ( head = bc->refs; head != NULL; head = next ) {
	    next = head->next;
	    if (( rbc = bdf->glyphs[head->gid] ) != NULL ) {
		head->bdfc = rbc;
		BCMakeDependent( bc,rbc );
		prev = head;
	    } else {
		LogError(_("Glyph %d in bitmap strike %d pixels refers to a missing glyph (%d)"),
		    bc->orig_pos, bdf->pixelsize, head->gid );
		if ( prev == NULL ) bc->refs = next;
		else prev->next = next;
	    }
	}
    }

}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

static const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

static const char *caps[] = { "butt", "round", "square", "inher", NULL };

static const char *spreads[] = { "pad", "reflect", "repeat", NULL };

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

static const char *charset_names[] = {
    "custom",
    "iso8859-1", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5",
    "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso8859-10",
    "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-16",
    "koi8-r",
    "jis201",
    "win", "mac", "symbol", "zapfding", "adobestandard",
    "jis208", "jis212", "ksc5601", "gb2312", "big5", "big5hkscs", "johab",
    "unicode", "unicode4", "sjis", "wansung", "gb2312pk", NULL};

static const char *unicode_interp_names[] = { "none", "adobe", "greek",
    "japanese", "tradchinese", "simpchinese", "korean", "ams", NULL };

#define STRIKE_PROPS	"strike.props"

#define GLYPH_EXT	".glyph"

#define BITMAP_EXT	".bitmap"

#define STRIKE_EXT	".strike"

static char base64[64] = {
 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

static const char *end_tt_instrs = "EndTTInstrs";

static void SFDConsumeUntil( FILE *sfd, const char** terminators );

static void SFDDumpRefs(FILE *sfd,RefChar *refs, int *newgids);

static void SFDDumpGuidelines(FILE *sfd,GuidelineSet *gl);

static RefChar *SFDGetRef(FILE *sfd, int was_enc);

static void SFDDumpImage(FILE *sfd,ImageList *img);

static void SFDDumpImagePNG(FILE *sfd,ImageList *img);


MacFeat *SFDParseMacFeatures(FILE *sfd, char *tok) {
    MacFeat *cur, *head=NULL, *last=NULL;
    struct macsetting *slast, *scur;
    int feat, ism, def, set;

    while ( strcmp(tok,"MacFeat:")==0 ) {
	cur = chunkalloc(sizeof(MacFeat));
	if ( last==NULL )
	    head = cur;
	else
	    last->next = cur;
	last = cur;

	getint(sfd,&feat); getint(sfd,&ism); getint(sfd, &def);
	cur->feature = feat; cur->ismutex = ism; cur->default_setting = def;
	getname(sfd,tok);
	cur->featname = SFDParseMacNames(sfd,tok);
	slast = NULL;
	while ( strcmp(tok,"MacSetting:")==0 ) {
	    scur = chunkalloc(sizeof(struct macsetting));
	    if ( slast==NULL )
		cur->settings = scur;
	    else
		slast->next = scur;
	    slast = scur;

	    getint(sfd,&set);
	    scur->setting = set;
	    getname(sfd,tok);
	    scur->setname = SFDParseMacNames(sfd,tok);
	}
    }
return( head );
}


static void SFDDumpHintList(FILE *sfd,const char *key, StemInfo *h) {
    HintInstance *hi;

    if ( h==NULL )
return;
    fprintf(sfd, "%s", key );
    for ( ; h!=NULL; h=h->next ) {
	fprintf(sfd, "%g %g", (double) h->start,(double) h->width );
	if ( h->ghost ) putc('G',sfd);
	if ( h->where!=NULL ) {
	    putc('<',sfd);
	    for ( hi=h->where; hi!=NULL; hi=hi->next )
		fprintf(sfd, "%g %g%c", (double) hi->begin, (double) hi->end, hi->next?' ':'>');
	}
	putc(h->next?' ':'\n',sfd);
    }
}


char *getquotedeol(FILE *sfd) {
    char *pt, *str, *end;
    int ch;

    pt = str = malloc(101); end = str+100;
    while ( isspace(ch = nlgetc(sfd)) && ch!='\r' && ch!='\n' );
    while ( ch!='\n' && ch!='\r' && ch!=EOF ) {
	if ( ch=='\\' ) {
	    /* We can't use nlgetc() here, because it would misinterpret */
	    /* double backslash at the end of line. Multiline strings,   */
	    /* broken with backslash + newline, are just handled above.  */
	    ch = getc(sfd);
	    if ( ch=='n' ) ch='\n';
	    /* else if ( ch=='\\' ) ch=='\\'; */ /* second backslash of '\\' */

	    /* FontForge doesn't write other escape sequences in this context. */
	    /* So any other value of ch is assumed impossible. */
	}
	if ( pt>=end ) {
	    pt = realloc(str,end-str+101);
	    end = pt+(end-str)+100;
	    str = pt;
	    pt = end-100;
	}
	*pt++ = ch;
	ch = nlgetc(sfd);
    }
    *pt='\0';
    /* these strings should be in utf8 now, but some old sfd files might have */
    /* latin1. Not a severe problems because they SHOULD be in ASCII. So any */
    /* non-ascii strings are erroneous anyway */
    if ( !utf8_valid(str) ) {
	pt = latin1_2_utf8_copy(str);
	free(str);
	str = pt;
    }
return( str );
}


static void SFRemoveDependencies(SplineFont *sf) {
    int i;
    struct splinecharlist *dlist, *dnext;
    KernPair *kp;

    for ( i=0; i<sf->glyphcnt; ++i ) if ( sf->glyphs[i]!=NULL ) {
	for ( dlist = sf->glyphs[i]->dependents; dlist!=NULL; dlist = dnext ) {
	    dnext = dlist->next;
	    chunkfree(dlist,sizeof(*dlist));
	}
	sf->glyphs[i]->dependents = NULL;
	for ( kp=sf->glyphs[i]->kerns; kp!=NULL; kp=kp->next ) {
	    kp->sc = (SplineChar *) (intpt) (kp->sc->orig_pos);
	    kp->kcid = true;		/* flag */
	}
	for ( kp=sf->glyphs[i]->vkerns; kp!=NULL; kp=kp->next ) {
	    kp->sc = (SplineChar *) (intpt) (kp->sc->orig_pos);
	    kp->kcid = true;
	}
    }
}