static OTLookup *CreateACLookup(SplineFont1 *sf,AnchorClass1 *ac) {
    OTLookup *otl = chunkalloc(sizeof(OTLookup));

    otl->lookup_type =
	    ac->ac.type == act_mark ? gpos_mark2base :
	    ac->ac.type == act_mkmk ? gpos_mark2mark :
	    ac->ac.type == act_curs ? gpos_cursive :
	    ac->ac.type == act_mklg ? gpos_mark2ligature :
		ot_undef;
    if ( otl->lookup_type == ot_undef )
	IError("Unknown AnchorClass type");
    otl->next = sf->sf.gpos_lookups;
    sf->sf.gpos_lookups = otl;
    otl->lookup_flags = ac->flags;
    otl->features = FeaturesFromTagSli(ac->feature_tag,ac->script_lang_index,sf);
    /* We will set the lookup_index after we've ordered the list */
    /* We will set the lookup_name after we've assigned the index */
    /* We will add one subtable soon */
return( otl );
}


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).

static int geteol(FILE *sfd, char *tokbuf) {
    char *pt=tokbuf, *end = tokbuf+2000-2; int ch;

    while ( isspace(ch = nlgetc(sfd)) && ch!='\r' && ch!='\n' );
    while ( ch!='\n' && ch!='\r' && ch!=EOF ) {
	if ( pt<end ) *pt++ = ch;
	ch = nlgetc(sfd);
    }
    *pt='\0';
return( pt!=tokbuf?1:ch==EOF?-1: 0 );
}

FontForge 20190801 has a use-after-free in SFD_GetFontMetaData in sfd.c.


static void SFDDumpDesignSize(FILE *sfd, SplineFont *sf) {
    struct otfname *on;

    if ( sf->design_size==0 )
return;

    fprintf( sfd, "DesignSize: %d", sf->design_size );
    if ( sf->fontstyle_id!=0 || sf->fontstyle_name!=NULL ||
	    sf->design_range_bottom!=0 || sf->design_range_top!=0 ) {
	fprintf( sfd, " %d-%d %d ",
		sf->design_range_bottom, sf->design_range_top,
		sf->fontstyle_id );
	for ( on=sf->fontstyle_name; on!=NULL; on=on->next ) {
	    fprintf( sfd, "%d ", on->lang );
	    SFDDumpUTF7Str(sfd, on->name);
	    if ( on->next!=NULL ) putc(' ',sfd);
	}
    }
    putc('\n',sfd);
}


static double SFDStartsCorrectly(FILE *sfd,char *tok) {
    real dval;
    int ch;

    if ( getname(sfd,tok)!=1 )
return( -1 );
    if ( strcmp(tok,"SplineFontDB:")!=0 )
return( -1 );
    if ( getreal(sfd,&dval)!=1 )
return( -1 );
    /* We don't yet generate version 4 of sfd. It will contain backslash */
    /*  newline in the middle of very long lines. I've put in code to parse */
    /*  this sequence, but I don't yet generate it. I want the parser to */
    /*  perculate through to users before I introduce the new format so there */
    /*  will be fewer complaints when it happens */
    // MIQ: getreal() can give some funky rounding errors it seems
    if ( dval!=0 && dval!=1 && dval!=2.0 && dval!=3.0
         && !(dval > 3.09 && dval <= 3.21)
         && dval!=4.0 )
    {
        LogError("Bad SFD Version number %.1f", dval );
return( -1 );
    }
    ch = nlgetc(sfd); ungetc(ch,sfd);
    if ( ch!='\r' && ch!='\n' )
return( -1 );

return( dval );
}


static HintInstance *SFDReadHintInstances(FILE *sfd, StemInfo *stem) {
    HintInstance *head=NULL, *last=NULL, *cur;
    real begin, end;
    int ch;

    while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
    if ( ch=='G' && stem != NULL ) {
	stem->ghost = true;
	while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
    }
    if ( ch!='<' ) {
	ungetc(ch,sfd);
return(NULL);
    }
    while ( getreal(sfd,&begin)==1 && getreal(sfd,&end)) {
	cur = chunkalloc(sizeof(HintInstance));
	cur->begin = begin;
	cur->end = end;
	if ( head == NULL )
	    head = cur;
	else
	    last->next = cur;
	last = cur;
    }
    while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
    if ( ch!='>' )
	ungetc(ch,sfd);
return( head );
}


static struct basescript *SFDParseBaseScript(FILE *sfd,struct Base *base) {
    struct basescript *bs;
    int i, ch;
    struct baselangextent *last, *cur;

    if ( base==NULL )
return(NULL);

    bs = chunkalloc(sizeof(struct basescript));

    bs->script = gettag(sfd);
    getint(sfd,&bs->def_baseline);
    if ( base->baseline_cnt!=0 ) {
	bs->baseline_pos = calloc(base->baseline_cnt,sizeof(int16));
	for ( i=0; i<base->baseline_cnt; ++i )
	    getsint(sfd, &bs->baseline_pos[i]);
    }
    while ( (ch=nlgetc(sfd))==' ' );
    last = NULL;
    while ( ch=='{' ) {
	ungetc(ch,sfd);
	cur = ParseBaseLang(sfd);
	if ( last==NULL )
	    bs->langs = cur;
	else
	    last->next = cur;
	last = cur;
	while ( (ch=nlgetc(sfd))==' ' );
    }
return( bs );
}


static OTLookup *FindNestedLookupByTag(SplineFont1 *sf,uint32 tag) {
    int isgpos;
    OTLookup *otl;

    for ( isgpos=0; isgpos<2; ++isgpos ) {
	for ( otl = isgpos ? sf->sf.gpos_lookups : sf->sf.gsub_lookups; otl!=NULL; otl=otl->next ) {
	    if ( otl->features!=NULL && otl->features->scripts==NULL &&
		    otl->features->featuretag == tag )
return( otl );
	}
    }
return( NULL );
}


int SFFindBiggestScriptLangIndex(SplineFont *_sf,uint32 script,uint32 lang) {
    int i, best_sli= -1, best_cnt= -1, cnt;
    SplineFont1 *sf = (SplineFont1 *) _sf;

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

    if ( sf->script_lang==NULL )
	SFGuessScriptList(sf);
    for ( i=0; sf->script_lang[i]!=NULL; ++i ) {
	if ( SLContains(sf->script_lang[i],script,lang)) {
	    cnt = SLCount(sf->script_lang[i]);
	    if ( cnt>best_cnt ) {
		best_sli = i;
		best_cnt = cnt;
	    }
	}
    }
    if ( best_sli==-1 )
return( SFAddScriptLangIndex(_sf,script,lang) );

return( best_sli );
}


static void SFDDumpLangName(FILE *sfd, struct ttflangname *ln) {
    int i, end;
    fprintf( sfd, "LangName: %d", ln->lang );
    for ( end = ttf_namemax; end>0 && ln->names[end-1]==NULL; --end );
    for ( i=0; i<end; ++i ) {
        putc(' ',sfd);
        SFDDumpUTF7Str(sfd,ln->names[i]);
    }
    putc('\n',sfd);
}


static void SFDGetDesignSize(FILE *sfd,SplineFont *sf) {
    int ch;
    struct otfname *cur;

    getsint(sfd,(int16 *) &sf->design_size);
    while ( (ch=nlgetc(sfd))==' ' );
    ungetc(ch,sfd);
    if ( isdigit(ch)) {
	getsint(sfd,(int16 *) &sf->design_range_bottom);
	while ( (ch=nlgetc(sfd))==' ' );
	if ( ch!='-' )
	    ungetc(ch,sfd);
	getsint(sfd,(int16 *) &sf->design_range_top);
	getsint(sfd,(int16 *) &sf->fontstyle_id);
	for (;;) {
	    while ( (ch=nlgetc(sfd))==' ' );
	    ungetc(ch,sfd);
	    if ( !isdigit(ch))
	break;
	    cur = chunkalloc(sizeof(struct otfname));
	    cur->next = sf->fontstyle_name;
	    sf->fontstyle_name = cur;
	    getsint(sfd,(int16 *) &cur->lang);
	    cur->name = SFDReadUTF7Str(sfd);
	}
    }
}


static Encoding *SFDGetEncoding(FILE *sfd, char *tok) {
    Encoding *enc = NULL;
    int encname;

    if ( getint(sfd,&encname) ) {
	if ( encname<(int)(sizeof(charset_names)/sizeof(charset_names[0])-1) )
	    enc = FindOrMakeEncoding(charset_names[encname]);
    } else {
	geteol(sfd,tok);
	enc = FindOrMakeEncoding(tok);
    }
    if ( enc==NULL )
	enc = &custom;
return( enc );
}


SplineFont *SFDRead(char *filename) {
return( SFD_Read(filename,NULL,false));
}


static ImageList *SFDGetImage(FILE *sfd) {
    /* We've read the image token */
    int width, height, image_type, bpl, clutlen, rlelen;
    uint32 trans;
    struct _GImage *base;
    GImage *image;
    ImageList *img;
    struct enc85 dec;
    int i, ch;

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

    getint(sfd,&width);
    getint(sfd,&height);
    getint(sfd,&image_type);
    getint(sfd,&bpl);
    getint(sfd,&clutlen);
    gethex(sfd,&trans);
    image = GImageCreate(image_type,width,height);
    base = image->list_len==0?image->u.image:image->u.images[0];
    img = calloc(1,sizeof(ImageList));
    img->image = image;
    getreal(sfd,&img->xoff);
    getreal(sfd,&img->yoff);
    getreal(sfd,&img->xscale);
    getreal(sfd,&img->yscale);
    while ( (ch=nlgetc(sfd))==' ' || ch=='\t' );
    ungetc(ch,sfd);
    rlelen = 0;
    if ( isdigit(ch))
	getint(sfd,&rlelen);
    base->trans = trans;
    if ( clutlen!=0 ) {
	if ( base->clut==NULL )
	    base->clut = calloc(1,sizeof(GClut));
	base->clut->clut_len = clutlen;
	base->clut->trans_index = trans;
	for ( i=0;i<clutlen; ++i ) {
	    int r,g,b;
	    r = Dec85(&dec);
	    g = Dec85(&dec);
	    b = Dec85(&dec);
	    base->clut->clut[i] = (r<<16)|(g<<8)|b;
	}
    }
    if ( rlelen!=0 ) {
	rle2image(&dec,rlelen,base);
    } else {
	for ( i=0; i<height; ++i ) {
	    if ( image_type==it_rgba ) {
		uint32 *ipt = (uint32 *) (base->data + i*base->bytes_per_line);
		uint32 *iend = (uint32 *) (base->data + (i+1)*base->bytes_per_line);
		int r,g,b, a;
		while ( ipt<iend ) {
		    a = Dec85(&dec);
		    r = Dec85(&dec);
		    g = Dec85(&dec);
		    b = Dec85(&dec);
		    *ipt++ = (a<<24)|(r<<16)|(g<<8)|b;
		}
	    } else if ( image_type==it_true ) {
		int *ipt = (int *) (base->data + i*base->bytes_per_line);
		int *iend = (int *) (base->data + (i+1)*base->bytes_per_line);
		int r,g,b;
		while ( ipt<iend ) {
		    r = Dec85(&dec);
		    g = Dec85(&dec);
		    b = Dec85(&dec);
		    *ipt++ = (r<<16)|(g<<8)|b;
		}
	    } else {
		uint8 *pt = (uint8 *) (base->data + i*base->bytes_per_line);
		uint8 *end = (uint8 *) (base->data + (i+1)*base->bytes_per_line);
		while ( pt<end ) {
		    *pt++ = Dec85(&dec);
		}
	    }
	}
    }
    img->bb.minx = img->xoff; img->bb.maxy = img->yoff;
    img->bb.maxx = img->xoff + GImageGetWidth(img->image)*img->xscale;
    img->bb.miny = img->yoff - GImageGetHeight(img->image)*img->yscale;
    /* In old sfd files I failed to recognize bitmap pngs as bitmap, so put */
    /*  in a little check here that converts things which should be bitmap to */
    /*  bitmap */ /* Eventually it can be removed as all old sfd files get */
    /*  converted. 22/10/2002 */
    if ( base->image_type==it_index && base->clut!=NULL && base->clut->clut_len==2 )
	img->image = ImageAlterClut(img->image);
return( img );
}


static void SFDGetNameList(FILE *sfd, char *tok, SplineFont *sf) {
    NameList *nl;

    geteol(sfd,tok);
    nl = NameListByName(tok);
    if ( nl==NULL )
	LogError(_("Failed to find NameList: %s"), tok);
    else
	sf->for_new_glyphs = nl;
}


static int SFDCloseCheck(SplinePointList *spl,int order2) {
    if ( spl->first!=spl->last &&
	    RealNear(spl->first->me.x,spl->last->me.x) &&
	    RealNear(spl->first->me.y,spl->last->me.y)) {
	SplinePoint *oldlast = spl->last;
	spl->first->prevcp = oldlast->prevcp;
	spl->first->noprevcp = oldlast->noprevcp;
	oldlast->prev->from->next = NULL;
	spl->last = oldlast->prev->from;
	chunkfree(oldlast->prev,sizeof(*oldlast));
	chunkfree(oldlast->hintmask,sizeof(HintMask));
	chunkfree(oldlast,sizeof(*oldlast));
	SplineMake(spl->last,spl->first,order2);
	spl->last = spl->first;
return( true );
    }
return( false );
}


static struct Base *SFDParseBase(FILE *sfd) {
    struct Base *base = chunkalloc(sizeof(struct Base));
    int i;

    getint(sfd,&base->baseline_cnt);
    if ( base->baseline_cnt!=0 ) {
	base->baseline_tags = malloc(base->baseline_cnt*sizeof(uint32));
	for ( i=0; i<base->baseline_cnt; ++i )
	    base->baseline_tags[i] = gettag(sfd);
    }
return( base );
}


void SFD_DumpPST( FILE *sfd, SplineChar *sc ) {
    PST *pst;

    for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
	if (( pst->subtable==NULL && pst->type!=pst_lcaret) || pst->type==pst_null )
	    /* Skip it */;
	else {
	    static const char *keywords[] = { "Null:", "Position2:", "PairPos2:",
		    "Substitution2:",
		    "AlternateSubs2:", "MultipleSubs2:", "Ligature2:",
		    "LCarets2:", NULL };
	    fprintf( sfd, "%s ", keywords[pst->type] );
	    if ( pst->subtable!=NULL ) {
		SFDDumpUTF7Str(sfd,pst->subtable->subtable_name);
		putc(' ',sfd);
	    }
	    if ( pst->type==pst_position ) {
		fprintf( sfd, "dx=%d dy=%d dh=%d dv=%d",
			pst->u.pos.xoff, pst->u.pos.yoff,
			pst->u.pos.h_adv_off, pst->u.pos.v_adv_off);
		SFDDumpValDevTab(sfd,pst->u.pos.adjust);
		putc('\n',sfd);
	    } else if ( pst->type==pst_pair ) {
		fprintf( sfd, "%s dx=%d dy=%d dh=%d dv=%d",
			pst->u.pair.paired,
			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 );
		SFDDumpValDevTab(sfd,pst->u.pair.vr[0].adjust);
		fprintf( sfd, " dx=%d dy=%d dh=%d dv=%d",
			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);
		SFDDumpValDevTab(sfd,pst->u.pair.vr[1].adjust);
		putc('\n',sfd);
	    } else if ( pst->type==pst_lcaret ) {
		int i;
		fprintf( sfd, "%d ", pst->u.lcaret.cnt );
		for ( i=0; i<pst->u.lcaret.cnt; ++i ) {
		    fprintf( sfd, "%d", pst->u.lcaret.carets[i] );
                    if ( i<pst->u.lcaret.cnt-1 ) putc(' ',sfd);
                }
		fprintf( sfd, "\n" );
	    } else
		fprintf( sfd, "%s\n", pst->u.lig.components );
	}
    }
}


bool SFD_GetFontMetaData( FILE *sfd,
			  char *tok,
			  SplineFont *sf,
			  SFD_GetFontMetaDataData* d )
{
    int ch;
    int i;
    KernClass* kc = 0;
    int old;
    char val[2000];

    // This allows us to assume we can dereference d
    // at all times
    static SFD_GetFontMetaDataData my_static_d;
    static int my_static_d_is_virgin = 1;
    if( !d )
    {
	if( my_static_d_is_virgin )
	{
	    my_static_d_is_virgin = 0;
	    SFD_GetFontMetaDataData_Init( &my_static_d );
	}
	d = &my_static_d;
    }

    if ( strmatch(tok,"FontName:")==0 )
    {
	geteol(sfd,val);
	sf->fontname = copy(val);
    }
    else if ( strmatch(tok,"FullName:")==0 )
    {
	geteol(sfd,val);
	sf->fullname = copy(val);
    }
    else if ( strmatch(tok,"FamilyName:")==0 )
    {
	geteol(sfd,val);
	sf->familyname = copy(val);
    }
    else if ( strmatch(tok,"DefaultBaseFilename:")==0 )
    {
	geteol(sfd,val);
	sf->defbasefilename = copy(val);
    }
    else if ( strmatch(tok,"Weight:")==0 )
    {
	getprotectedname(sfd,val);
	sf->weight = copy(val);
    }
    else if ( strmatch(tok,"Copyright:")==0 )
    {
	sf->copyright = getquotedeol(sfd);
    }
    else if ( strmatch(tok,"Comments:")==0 )
    {
	char *temp = getquotedeol(sfd);
	sf->comments = latin1_2_utf8_copy(temp);
	free(temp);
    }
    else if ( strmatch(tok,"UComments:")==0 )
    {
	sf->comments = SFDReadUTF7Str(sfd);
    }
    else if ( strmatch(tok,"FontLog:")==0 )
    {
	sf->fontlog = SFDReadUTF7Str(sfd);
    }
    else if ( strmatch(tok,"Version:")==0 )
    {
	geteol(sfd,val);
	sf->version = copy(val);
    }
    else if ( strmatch(tok,"StyleMapFamilyName:")==0 )
    {
    sf->styleMapFamilyName = SFDReadUTF7Str(sfd);
    }
    /* Legacy attribute for StyleMapFamilyName. Deprecated. */
    else if ( strmatch(tok,"OS2FamilyName:")==0 )
    {
    if (sf->styleMapFamilyName == NULL)
        sf->styleMapFamilyName = SFDReadUTF7Str(sfd);
    }
    else if ( strmatch(tok,"FONDName:")==0 )
    {
	geteol(sfd,val);
	sf->fondname = copy(val);
    }
    else if ( strmatch(tok,"ItalicAngle:")==0 )
    {
	getreal(sfd,&sf->italicangle);
    }
    else if ( strmatch(tok,"StrokeWidth:")==0 )
    {
	getreal(sfd,&sf->strokewidth);
    }
    else if ( strmatch(tok,"UnderlinePosition:")==0 )
    {
	getreal(sfd,&sf->upos);
    }
    else if ( strmatch(tok,"UnderlineWidth:")==0 )
    {
	getreal(sfd,&sf->uwidth);
    }
    else if ( strmatch(tok,"ModificationTime:")==0 )
    {
	getlonglong(sfd,&sf->modificationtime);
    }
    else if ( strmatch(tok,"CreationTime:")==0 )
    {
	getlonglong(sfd,&sf->creationtime);
	d->hadtimes = true;
    }
    else if ( strmatch(tok,"PfmFamily:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->pfminfo.pfmfamily = temp;
	sf->pfminfo.pfmset = true;
    }
    else if ( strmatch(tok,"LangName:")==0 )
    {
	sf->names = SFDGetLangName(sfd,sf->names);
    }
    else if ( strmatch(tok,"GaspTable:")==0 )
    {
	SFDGetGasp(sfd,sf);
    }
    else if ( strmatch(tok,"DesignSize:")==0 )
    {
	SFDGetDesignSize(sfd,sf);
    }
    else if ( strmatch(tok,"OtfFeatName:")==0 )
    {
	SFDGetOtfFeatName(sfd,sf);
    }
    else if ( strmatch(tok,"PfmWeight:")==0 || strmatch(tok,"TTFWeight:")==0 )
    {
	getsint(sfd,&sf->pfminfo.weight);
	sf->pfminfo.pfmset = true;
    }
    else if ( strmatch(tok,"TTFWidth:")==0 )
    {
	getsint(sfd,&sf->pfminfo.width);
	sf->pfminfo.pfmset = true;
    }
    else if ( strmatch(tok,"Panose:")==0 )
    {
	int temp,i;
	for ( i=0; i<10; ++i )
	{
	    getint(sfd,&temp);
	    sf->pfminfo.panose[i] = temp;
	}
	sf->pfminfo.panose_set = true;
    }
    else if ( strmatch(tok,"LineGap:")==0 )
    {
	getsint(sfd,&sf->pfminfo.linegap);
	sf->pfminfo.pfmset = true;
    }
    else if ( strmatch(tok,"VLineGap:")==0 )
    {
	getsint(sfd,&sf->pfminfo.vlinegap);
	sf->pfminfo.pfmset = true;
    }
    else if ( strmatch(tok,"HheadAscent:")==0 )
    {
	getsint(sfd,&sf->pfminfo.hhead_ascent);
    }
    else if ( strmatch(tok,"HheadAOffset:")==0 )
    {
	int temp;
	getint(sfd,&temp); sf->pfminfo.hheadascent_add = temp;
    }
    else if ( strmatch(tok,"HheadDescent:")==0 )
    {
	getsint(sfd,&sf->pfminfo.hhead_descent);
    }
    else if ( strmatch(tok,"HheadDOffset:")==0 )
    {
	int temp;
	getint(sfd,&temp); sf->pfminfo.hheaddescent_add = temp;
    }
    else if ( strmatch(tok,"OS2TypoLinegap:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_typolinegap);
    }
    else if ( strmatch(tok,"OS2TypoAscent:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_typoascent);
    }
    else if ( strmatch(tok,"OS2TypoAOffset:")==0 )
    {
	int temp;
	getint(sfd,&temp); sf->pfminfo.typoascent_add = temp;
    }
    else if ( strmatch(tok,"OS2TypoDescent:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_typodescent);
    }
    else if ( strmatch(tok,"OS2TypoDOffset:")==0 )
    {
	int temp;
	getint(sfd,&temp); sf->pfminfo.typodescent_add = temp;
    }
    else if ( strmatch(tok,"OS2WinAscent:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_winascent);
    }
    else if ( strmatch(tok,"OS2WinDescent:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_windescent);
    }
    else if ( strmatch(tok,"OS2WinAOffset:")==0 )
    {
	int temp;
	getint(sfd,&temp); sf->pfminfo.winascent_add = temp;
    }
    else if ( strmatch(tok,"OS2WinDOffset:")==0 )
    {
	int temp;
	getint(sfd,&temp); sf->pfminfo.windescent_add = temp;
    }
    else if ( strmatch(tok,"HHeadAscent:")==0 )
    {
	// DUPLICATE OF ABOVE
	getsint(sfd,&sf->pfminfo.hhead_ascent);
    }
    else if ( strmatch(tok,"HHeadDescent:")==0 )
    {
	// DUPLICATE OF ABOVE
	getsint(sfd,&sf->pfminfo.hhead_descent);
    }

    else if ( strmatch(tok,"HHeadAOffset:")==0 )
    {
	// DUPLICATE OF ABOVE
	int temp;
	getint(sfd,&temp); sf->pfminfo.hheadascent_add = temp;
    }
    else if ( strmatch(tok,"HHeadDOffset:")==0 )
    {
	// DUPLICATE OF ABOVE
	int temp;
	getint(sfd,&temp); sf->pfminfo.hheaddescent_add = temp;
    }
    else if ( strmatch(tok,"MacStyle:")==0 )
    {
	getsint(sfd,&sf->macstyle);
    }
    else if ( strmatch(tok,"OS2SubXSize:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_subxsize);
	sf->pfminfo.subsuper_set = true;
    }
    else if ( strmatch(tok,"OS2SubYSize:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_subysize);
    }
    else if ( strmatch(tok,"OS2SubXOff:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_subxoff);
    }
    else if ( strmatch(tok,"OS2SubYOff:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_subyoff);
    }
    else if ( strmatch(tok,"OS2SupXSize:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_supxsize);
    }
    else if ( strmatch(tok,"OS2SupYSize:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_supysize);
    }
    else if ( strmatch(tok,"OS2SupXOff:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_supxoff);
    }
    else if ( strmatch(tok,"OS2SupYOff:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_supyoff);
    }
    else if ( strmatch(tok,"OS2StrikeYSize:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_strikeysize);
    }
    else if ( strmatch(tok,"OS2StrikeYPos:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_strikeypos);
    }
    else if ( strmatch(tok,"OS2CapHeight:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_capheight);
    }
    else if ( strmatch(tok,"OS2XHeight:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_xheight);
    }
    else if ( strmatch(tok,"OS2FamilyClass:")==0 )
    {
	getsint(sfd,&sf->pfminfo.os2_family_class);
    }
    else if ( strmatch(tok,"OS2Vendor:")==0 )
    {
	while ( isspace(nlgetc(sfd)));
	sf->pfminfo.os2_vendor[0] = nlgetc(sfd);
	sf->pfminfo.os2_vendor[1] = nlgetc(sfd);
	sf->pfminfo.os2_vendor[2] = nlgetc(sfd);
	sf->pfminfo.os2_vendor[3] = nlgetc(sfd);
	(void) nlgetc(sfd);
    }
    else if ( strmatch(tok,"OS2CodePages:")==0 )
    {
	gethexints(sfd,sf->pfminfo.codepages,2);
	sf->pfminfo.hascodepages = true;
    }
    else if ( strmatch(tok,"OS2UnicodeRanges:")==0 )
    {
	gethexints(sfd,sf->pfminfo.unicoderanges,4);
	sf->pfminfo.hasunicoderanges = true;
    }
    else if ( strmatch(tok,"TopEncoding:")==0 )
    {
	/* Obsolete */
	getint(sfd,&sf->top_enc);
    }
    else if ( strmatch(tok,"Ascent:")==0 )
    {
	getint(sfd,&sf->ascent);
    }
    else if ( strmatch(tok,"Descent:")==0 )
    {
	getint(sfd,&sf->descent);
    }
    else if ( strmatch(tok,"InvalidEm:")==0 )
    {
	getint(sfd,&sf->invalidem);
    }
    else if ( strmatch(tok,"woffMajor:")==0 )
    {
	getint(sfd,&sf->woffMajor);
    }
    else if ( strmatch(tok,"woffMinor:")==0 )
    {
	getint(sfd,&sf->woffMinor);
    }
    else if ( strmatch(tok,"woffMetadata:")==0 )
    {
	sf->woffMetadata = SFDReadUTF7Str(sfd);
    }
    else if ( strmatch(tok,"UFOAscent:")==0 )
    {
	    getreal(sfd,&sf->ufo_ascent);
    }
    else if ( strmatch(tok,"UFODescent:")==0 )
    {
	getreal(sfd,&sf->ufo_descent);
    }
    else if ( strmatch(tok,"sfntRevision:")==0 )
    {
	gethex(sfd,(uint32 *)&sf->sfntRevision);
    }
    else if ( strmatch(tok,"LayerCount:")==0 )
    {
	d->had_layer_cnt = true;
	int layer_cnt_tmp;
	getint(sfd,&layer_cnt_tmp);
	if ( layer_cnt_tmp>2 ) {
	    sf->layers = realloc(sf->layers,sf->layer_cnt*sizeof(LayerInfo));
	    memset(sf->layers+2,0,(sf->layer_cnt-2)*sizeof(LayerInfo));
	    sf->layer_cnt = layer_cnt_tmp;
	}
    }
    else if ( strmatch(tok,"Layer:")==0 )
    {
        // TODO: Read the U. F. O. path.
	int layer, o2, bk;
	getint(sfd,&layer);
	if ( layer>=sf->layer_cnt ) {
	    sf->layers = realloc(sf->layers,(layer+1)*sizeof(LayerInfo));
	    memset(sf->layers+sf->layer_cnt,0,((layer+1)-sf->layer_cnt)*sizeof(LayerInfo));
	    sf->layer_cnt = layer+1;
	}
	getint(sfd,&o2);
	sf->layers[layer].order2 = o2;
	sf->layers[layer].background = layer==ly_back;
	/* Used briefly, now background is after layer name */
	while ( (ch=nlgetc(sfd))==' ' );
	ungetc(ch,sfd);
	if ( ch!='"' ) {
	    getint(sfd,&bk);
	    sf->layers[layer].background = bk;
	}
	/* end of section for obsolete format */
	sf->layers[layer].name = SFDReadUTF7Str(sfd);
	while ( (ch=nlgetc(sfd))==' ' );
	ungetc(ch,sfd);
	if ( ch!='\n' ) {
	    getint(sfd,&bk);
	    sf->layers[layer].background = bk;
	}
	while ( (ch=nlgetc(sfd))==' ' );
	ungetc(ch,sfd);
	if ( ch!='\n' ) { sf->layers[layer].ufo_path = SFDReadUTF7Str(sfd); }
    }
    else if ( strmatch(tok,"PreferredKerning:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->preferred_kerning = temp;
    }
    else if ( strmatch(tok,"StrokedFont:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->strokedfont = temp;
    }
    else if ( strmatch(tok,"MultiLayer:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->multilayer = temp;
    }
    else if ( strmatch(tok,"NeedsXUIDChange:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->changed_since_xuidchanged = temp;
    }
    else if ( strmatch(tok,"VerticalOrigin:")==0 )
    {
	// this doesn't seem to be written ever.
	int temp;
	getint(sfd,&temp);
	sf->hasvmetrics = true;
    }
    else if ( strmatch(tok,"HasVMetrics:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->hasvmetrics = temp;
    }
    else if ( strmatch(tok,"Justify:")==0 )
    {
	SFDParseJustify(sfd,sf,tok);
    }
    else if ( strmatch(tok,"BaseHoriz:")==0 )
    {
	sf->horiz_base = SFDParseBase(sfd);
	d->last_base = sf->horiz_base;
	d->last_base_script = NULL;
    }
    else if ( strmatch(tok,"BaseVert:")==0 )
    {
	sf->vert_base = SFDParseBase(sfd);
	d->last_base = sf->vert_base;
	d->last_base_script = NULL;
    }
    else if ( strmatch(tok,"BaseScript:")==0 )
    {
	struct basescript *bs = SFDParseBaseScript(sfd,d->last_base);
	if ( d->last_base==NULL )
	{
	    BaseScriptFree(bs);
	    bs = NULL;
	}
	else if ( d->last_base_script!=NULL )
	    d->last_base_script->next = bs;
	else
	    d->last_base->scripts = bs;
	d->last_base_script = bs;
    }
    else if ( strmatch(tok,"StyleMap:")==0 )
    {
    gethex(sfd,(uint32 *)&sf->pfminfo.stylemap);
    }
    /* Legacy attribute for StyleMap. Deprecated. */
    else if ( strmatch(tok,"OS2StyleName:")==0 )
    {
    char* sname = SFDReadUTF7Str(sfd);
    if (sf->pfminfo.stylemap == -1) {
        if (strcmp(sname,"bold italic")==0) sf->pfminfo.stylemap = 0x21;
        else if (strcmp(sname,"bold")==0) sf->pfminfo.stylemap = 0x20;
        else if (strcmp(sname,"italic")==0) sf->pfminfo.stylemap = 0x01;
        else if (strcmp(sname,"regular")==0) sf->pfminfo.stylemap = 0x40;
    }
    free(sname);
    }
    else if ( strmatch(tok,"FSType:")==0 )
    {
	getsint(sfd,&sf->pfminfo.fstype);
    }
    else if ( strmatch(tok,"OS2Version:")==0 )
    {
	getsint(sfd,&sf->os2_version);
    }
    else if ( strmatch(tok,"OS2_WeightWidthSlopeOnly:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->weight_width_slope_only = temp;
    }
    else if ( strmatch(tok,"OS2_UseTypoMetrics:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->use_typo_metrics = temp;
    }
    else if ( strmatch(tok,"UseUniqueID:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->use_uniqueid = temp;
    }
    else if ( strmatch(tok,"UseXUID:")==0 )
    {
	int temp;
	getint(sfd,&temp);
	sf->use_xuid = temp;
    }
    else if ( strmatch(tok,"UniqueID:")==0 )
    {
	getint(sfd,&sf->uniqueid);
    }
    else if ( strmatch(tok,"XUID:")==0 )
    {
	geteol(sfd,tok);
	sf->xuid = copy(tok);
    }
    else if ( strmatch(tok,"Lookup:")==0 )
    {
	OTLookup *otl;
	int temp;
	if ( sf->sfd_version<2 ) {
	    IError( "Lookups should not happen in version 1 sfd files." );
	    exit(1);
	}
	otl = chunkalloc(sizeof(OTLookup));
	getint(sfd,&temp); otl->lookup_type = temp;
	getint(sfd,&temp); otl->lookup_flags = temp;
	getint(sfd,&temp); otl->store_in_afm = temp;
	otl->lookup_name = SFDReadUTF7Str(sfd);
	if ( otl->lookup_type<gpos_single ) {
	    if ( d->lastsotl==NULL )
		sf->gsub_lookups = otl;
	    else
		d->lastsotl->next = otl;
	    d->lastsotl = otl;
	} else {
	    if ( d->lastpotl==NULL )
		sf->gpos_lookups = otl;
	    else
		d->lastpotl->next = otl;
	    d->lastpotl = otl;
	}
	SFDParseLookup(sfd,otl);
    }
    else if ( strmatch(tok,"MarkAttachClasses:")==0 )
    {
	getint(sfd,&sf->mark_class_cnt);
	sf->mark_classes = malloc(sf->mark_class_cnt*sizeof(char *));
	sf->mark_class_names = malloc(sf->mark_class_cnt*sizeof(char *));
	sf->mark_classes[0] = NULL; sf->mark_class_names[0] = NULL;
	for ( i=1; i<sf->mark_class_cnt; ++i )
	{
	    /* Class 0 is unused */
	    int temp;
	    while ( (temp=nlgetc(sfd))=='\n' || temp=='\r' ); ungetc(temp,sfd);
	    sf->mark_class_names[i] = SFDReadUTF7Str(sfd);
	    getint(sfd,&temp);
	    sf->mark_classes[i] = malloc(temp+1); sf->mark_classes[i][temp] = '\0';
	    nlgetc(sfd);	/* skip space */
	    fread(sf->mark_classes[i],1,temp,sfd);
	}
    }
    else if ( strmatch(tok,"MarkAttachSets:")==0 )
    {
	getint(sfd,&sf->mark_set_cnt);
	sf->mark_sets = malloc(sf->mark_set_cnt*sizeof(char *));
	sf->mark_set_names = malloc(sf->mark_set_cnt*sizeof(char *));
	for ( i=0; i<sf->mark_set_cnt; ++i )
	{
	    /* Set 0 is used */
	    int temp;
	    while ( (temp=nlgetc(sfd))=='\n' || temp=='\r' ); ungetc(temp,sfd);
	    sf->mark_set_names[i] = SFDReadUTF7Str(sfd);
	    getint(sfd,&temp);
	    sf->mark_sets[i] = malloc(temp+1); sf->mark_sets[i][temp] = '\0';
	    nlgetc(sfd);	/* skip space */
	    fread(sf->mark_sets[i],1,temp,sfd);
	}
    }
    else if ( strmatch(tok,"KernClass2:")==0 || strmatch(tok,"VKernClass2:")==0 ||
	      strmatch(tok,"KernClass:")==0 || strmatch(tok,"VKernClass:")==0 ||
	      strmatch(tok,"KernClass3:")==0 || strmatch(tok,"VKernClass3:")==0 )
    {
	int kernclassversion = 0;
	int isv = tok[0]=='V';
	int kcvoffset = (isv ? 10 : 9); //Offset to read kerning class version
	if (isdigit(tok[kcvoffset])) kernclassversion = tok[kcvoffset] - '0';
	int temp, classstart=1;
	int old = (kernclassversion == 0);

	if ( (sf->sfd_version<2)!=old ) {
	    IError( "Version mixup in Kerning Classes of sfd file." );
	    exit(1);
	}
	kc = chunkalloc(old ? sizeof(KernClass1) : sizeof(KernClass));
	getint(sfd,&kc->first_cnt);
	ch=nlgetc(sfd);
	if ( ch=='+' )
	    classstart = 0;
	else
	    ungetc(ch,sfd);
	getint(sfd,&kc->second_cnt);
	if ( old ) {
	    getint(sfd,&temp); ((KernClass1 *) kc)->sli = temp;
	    getint(sfd,&temp); ((KernClass1 *) kc)->flags = temp;
	} else {
	    kc->subtable = SFFindLookupSubtableAndFreeName(sf,SFDReadUTF7Str(sfd));
	    if ( kc->subtable!=NULL && kc->subtable->kc==NULL )
		kc->subtable->kc = kc;
	    else {
		if ( kc->subtable==NULL )
		    LogError(_("Bad SFD file, missing subtable in kernclass defn.\n") );
		else
		    LogError(_("Bad SFD file, two kerning classes assigned to the same subtable: %s\n"), kc->subtable->subtable_name );
		kc->subtable = NULL;
	    }
	}
	kc->firsts = calloc(kc->first_cnt,sizeof(char *));
	kc->seconds = calloc(kc->second_cnt,sizeof(char *));
	kc->offsets = calloc(kc->first_cnt*kc->second_cnt,sizeof(int16));
	kc->adjusts = calloc(kc->first_cnt*kc->second_cnt,sizeof(DeviceTable));
	if (kernclassversion >= 3) {
	  kc->firsts_flags = calloc(kc->first_cnt, sizeof(int));
	  kc->seconds_flags = calloc(kc->second_cnt, sizeof(int));
	  kc->offsets_flags = calloc(kc->first_cnt*kc->second_cnt, sizeof(int));
	  kc->firsts_names = calloc(kc->first_cnt, sizeof(char*));
	  kc->seconds_names = calloc(kc->second_cnt, sizeof(char*));
	}
	kc->firsts[0] = NULL;
	for ( i=classstart; i<kc->first_cnt; ++i ) {
	  if (kernclassversion < 3) {
	    getint(sfd,&temp);
	    kc->firsts[i] = malloc(temp+1); kc->firsts[i][temp] = '\0';
	    nlgetc(sfd);	/* skip space */
	    fread(kc->firsts[i],1,temp,sfd);
	  } else {
	    getint(sfd,&kc->firsts_flags[i]);
	    while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd); if (ch == '\n' || ch == EOF) continue;
	    kc->firsts_names[i] = SFDReadUTF7Str(sfd);
	    while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd); if (ch == '\n' || ch == EOF) continue;
	    kc->firsts[i] = SFDReadUTF7Str(sfd);
            if (kc->firsts[i] == NULL) kc->firsts[i] = copy(""); // In certain places, this must be defined.
	    while ((ch=nlgetc(sfd)) == ' ' || ch == '\n'); ungetc(ch, sfd);
	  }
	}
	kc->seconds[0] = NULL;
	for ( i=1; i<kc->second_cnt; ++i ) {
	  if (kernclassversion < 3) {
	    getint(sfd,&temp);
	    kc->seconds[i] = malloc(temp+1); kc->seconds[i][temp] = '\0';
	    nlgetc(sfd);	/* skip space */
	    fread(kc->seconds[i],1,temp,sfd);
	  } else {
	    getint(sfd,&temp);
	    kc->seconds_flags[i] = temp;
	    while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd); if (ch == '\n' || ch == EOF) continue;
	    kc->seconds_names[i] = SFDReadUTF7Str(sfd);
	    while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd); if (ch == '\n' || ch == EOF) continue;
	    kc->seconds[i] = SFDReadUTF7Str(sfd);
            if (kc->seconds[i] == NULL) kc->seconds[i] = copy(""); // In certain places, this must be defined.
	    while ((ch=nlgetc(sfd)) == ' ' || ch == '\n'); ungetc(ch, sfd);
	  }
	}
	for ( i=0; i<kc->first_cnt*kc->second_cnt; ++i ) {
	  if (kernclassversion >= 3) {
	    getint(sfd,&temp);
	    kc->offsets_flags[i] = temp;
	  }
	    getint(sfd,&temp);
	    kc->offsets[i] = temp;
	    SFDReadDeviceTable(sfd,&kc->adjusts[i]);
	}
	if ( !old && kc->subtable == NULL ) {
	    /* Error. Ignore it. Free it. Whatever */;
	} else if ( !isv ) {
	    if ( d->lastkc==NULL )
		sf->kerns = kc;
	    else
		d->lastkc->next = kc;
	    d->lastkc = kc;
	} else {
	    if ( d->lastvkc==NULL )
		sf->vkerns = kc;
	    else
		d->lastvkc->next = kc;
	    d->lastvkc = kc;
	}
    }
    else if ( strmatch(tok,"ContextPos2:")==0 || strmatch(tok,"ContextSub2:")==0 ||
	      strmatch(tok,"ChainPos2:")==0 || strmatch(tok,"ChainSub2:")==0 ||
	      strmatch(tok,"ReverseChain2:")==0 ||
	      strmatch(tok,"ContextPos:")==0 || strmatch(tok,"ContextSub:")==0 ||
	      strmatch(tok,"ChainPos:")==0 || strmatch(tok,"ChainSub:")==0 ||
	      strmatch(tok,"ReverseChain:")==0 )
    {
	FPST *fpst;
	int old;
	if ( strchr(tok,'2')!=NULL ) {
	    old = false;
	    fpst = chunkalloc(sizeof(FPST));
	} else {
	    old = true;
	    fpst = chunkalloc(sizeof(FPST1));
	}
	if ( (sf->sfd_version<2)!=old ) {
	    IError( "Version mixup in FPST of sfd file." );
	    exit(1);
	}
	if ( d->lastfp==NULL )
	    sf->possub = fpst;
	else
	    d->lastfp->next = fpst;
	d->lastfp = fpst;
	SFDParseChainContext(sfd,sf,fpst,tok,old);
    }
    else if ( strmatch(tok,"Group:")==0 ) {
        struct ff_glyphclasses *grouptmp = calloc(1, sizeof(struct ff_glyphclasses));
        while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd);
        grouptmp->classname = SFDReadUTF7Str(sfd);
        while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd);
        grouptmp->glyphs = SFDReadUTF7Str(sfd);
        while ((ch=nlgetc(sfd)) == ' ' || ch == '\n'); ungetc(ch, sfd);
        if (d->lastgroup != NULL) d->lastgroup->next = grouptmp; else sf->groups = grouptmp;
        d->lastgroup = grouptmp;
    }
    else if ( strmatch(tok,"GroupKern:")==0 ) {
        int temp = 0;
        struct ff_rawoffsets *kerntmp = calloc(1, sizeof(struct ff_rawoffsets));
        while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd);
        kerntmp->left = SFDReadUTF7Str(sfd);
        while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd);
        kerntmp->right = SFDReadUTF7Str(sfd);
        while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd);
        getint(sfd,&temp);
        kerntmp->offset = temp;
        while ((ch=nlgetc(sfd)) == ' ' || ch == '\n'); ungetc(ch, sfd);
        if (d->lastgroupkern != NULL) d->lastgroupkern->next = kerntmp; else sf->groupkerns = kerntmp;
        d->lastgroupkern = kerntmp;
    }
    else if ( strmatch(tok,"GroupVKern:")==0 ) {
        int temp = 0;
        struct ff_rawoffsets *kerntmp = calloc(1, sizeof(struct ff_rawoffsets));
        while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd);
        kerntmp->left = SFDReadUTF7Str(sfd);
        while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd);
        kerntmp->right = SFDReadUTF7Str(sfd);
        while ((ch=nlgetc(sfd)) == ' '); ungetc(ch, sfd);
        getint(sfd,&temp);
        kerntmp->offset = temp;
        while ((ch=nlgetc(sfd)) == ' ' || ch == '\n'); ungetc(ch, sfd);
        if (d->lastgroupvkern != NULL) d->lastgroupvkern->next = kerntmp; else sf->groupvkerns = kerntmp;
        d->lastgroupvkern = kerntmp;
    }
    else if ( strmatch(tok,"MacIndic2:")==0 || strmatch(tok,"MacContext2:")==0 ||
	      strmatch(tok,"MacLigature2:")==0 || strmatch(tok,"MacSimple2:")==0 ||
	      strmatch(tok,"MacKern2:")==0 || strmatch(tok,"MacInsert2:")==0 ||
	      strmatch(tok,"MacIndic:")==0 || strmatch(tok,"MacContext:")==0 ||
	      strmatch(tok,"MacLigature:")==0 || strmatch(tok,"MacSimple:")==0 ||
	      strmatch(tok,"MacKern:")==0 || strmatch(tok,"MacInsert:")==0 )
    {
	ASM *sm;
	if ( strchr(tok,'2')!=NULL ) {
	    old = false;
	    sm = chunkalloc(sizeof(ASM));
	} else {
	    old = true;
	    sm = chunkalloc(sizeof(ASM1));
	}
	if ( (sf->sfd_version<2)!=old ) {
	    IError( "Version mixup in state machine of sfd file." );
	    exit(1);
	}
	if ( d->lastsm==NULL )
	    sf->sm = sm;
	else
	    d->lastsm->next = sm;
	d->lastsm = sm;
	SFDParseStateMachine(sfd,sf,sm,tok,old);
    }
    else if ( strmatch(tok,"MacFeat:")==0 )
    {
	sf->features = SFDParseMacFeatures(sfd,tok);
    }
    else if ( strmatch(tok,"TtfTable:")==0 )
    {
	/* Old, binary format */
	/* still used for maxp and unknown tables */
	SFDGetTtfTable(sfd,sf,d->lastttf);
    }
    else if ( strmatch(tok,"TtTable:")==0 )
    {
	/* text instruction format */
	SFDGetTtTable(sfd,sf,d->lastttf);
    }

    ///////////////////

    else if ( strmatch(tok,"ShortTable:")==0 )
    {
	// only read, not written.
	/* text number format */
	SFDGetShortTable(sfd,sf,d->lastttf);
    }
    else
    {
        //
        // We didn't have a match ourselves.
        //
        return false;
    }
    return true;
}


static int getprotectedname(FILE *sfd, char *tokbuf) {
    char *pt=tokbuf, *end = tokbuf+100-2; int ch;

    while ( (ch = nlgetc(sfd))==' ' || ch=='\t' );
    while ( ch!=EOF && !isspace(ch) && ch!='[' && ch!=']' && ch!='{' && ch!='}' && ch!='<' && ch!='%' ) {
	if ( pt<end ) *pt++ = ch;
	ch = nlgetc(sfd);
    }
    if ( pt==tokbuf && ch!=EOF )
	*pt++ = ch;
    else
	ungetc(ch,sfd);
    *pt='\0';
return( pt!=tokbuf?1:ch==EOF?-1: 0 );
}


static int getint(FILE *sfd, int *val) {
    char tokbuf[100]; int ch;
    char *pt=tokbuf, *end = tokbuf+100-2;

    while ( isspace(ch = nlgetc(sfd)));
    if ( ch=='-' || ch=='+' ) {
	*pt++ = ch;
	ch = nlgetc(sfd);
    }
    while ( isdigit(ch)) {
	if ( pt<end ) *pt++ = ch;
	ch = nlgetc(sfd);
    }
    *pt='\0';
    ungetc(ch,sfd);
    *val = strtol(tokbuf,NULL,10);
return( pt!=tokbuf?1:ch==EOF?-1: 0 );
}


static void SFDGetOtfFeatName(FILE *sfd,SplineFont *sf) {
    int ch;
    struct otfname *cur;
    struct otffeatname *fn;

    fn = chunkalloc(sizeof(struct otffeatname));
    fn->tag = gettag(sfd);
    for (;;) {
	while ( (ch=nlgetc(sfd))==' ' );
	ungetc(ch,sfd);
	if ( !isdigit(ch))
    break;
	cur = chunkalloc(sizeof(struct otfname));
	cur->next = fn->names;
	fn->names = cur;
	getsint(sfd,(int16 *) &cur->lang);
	cur->name = SFDReadUTF7Str(sfd);
    }
    fn->next = sf->feat_names;
    sf->feat_names = fn;
}


static enum uni_interp SFDGetUniInterp(FILE *sfd, char *tok, SplineFont *sf) {
    int uniinterp = ui_none;
    int i;

    geteol(sfd,tok);
    for ( i=0; unicode_interp_names[i]!=NULL; ++i )
	if ( strcmp(tok,unicode_interp_names[i])==0 ) {
	    uniinterp = i;
    break;
	}
    /* These values are now handled by namelists */
    if ( uniinterp == ui_adobe ) {
	sf->for_new_glyphs = NameListByName("AGL with PUA");
	uniinterp = ui_none;
    } else if ( uniinterp == ui_greek ) {
	sf->for_new_glyphs = NameListByName("Greek small caps");
	uniinterp = ui_none;
    } else if ( uniinterp == ui_ams ) {
	sf->for_new_glyphs = NameListByName("AMS Names");
	uniinterp = ui_none;
    }

return( uniinterp );
}


static void SFDParseStateMachine(FILE *sfd,SplineFont *sf,ASM *sm, char *tok,int old) {
    int i, temp;

    sm->type = strnmatch(tok,"MacIndic",8)==0 ? asm_indic :
		strnmatch(tok,"MacContext",10)==0 ? asm_context :
		strnmatch(tok,"MacLigature",11)==0 ? asm_lig :
		strnmatch(tok,"MacSimple",9)==0 ? asm_simple :
		strnmatch(tok,"MacKern",7)==0 ? asm_kern : asm_insert;
    if ( old ) {
	getusint(sfd,&((ASM1 *) sm)->feature);
	nlgetc(sfd);		/* Skip comma */
	getusint(sfd,&((ASM1 *) sm)->setting);
    } else {
	sm->subtable = SFFindLookupSubtableAndFreeName(sf,SFDReadUTF7Str(sfd));
	sm->subtable->sm = sm;
    }
    getusint(sfd,&sm->flags);
    getusint(sfd,&sm->class_cnt);
    getusint(sfd,&sm->state_cnt);

    sm->classes = malloc(sm->class_cnt*sizeof(char *));
    sm->classes[0] = sm->classes[1] = sm->classes[2] = sm->classes[3] = NULL;
    for ( i=4; i<sm->class_cnt; ++i ) {
	getname(sfd,tok);
	getint(sfd,&temp);
	sm->classes[i] = malloc(temp+1); sm->classes[i][temp] = '\0';
	nlgetc(sfd);	/* skip space */
	fread(sm->classes[i],1,temp,sfd);
    }

    sm->state = malloc(sm->class_cnt*sm->state_cnt*sizeof(struct asm_state));
    for ( i=0; i<sm->class_cnt*sm->state_cnt; ++i ) {
	getusint(sfd,&sm->state[i].next_state);
	getusint(sfd,&sm->state[i].flags);
	if ( sm->type == asm_context ) {
	    sm->state[i].u.context.mark_lookup = SFD_ParseNestedLookup(sfd,sf,old);
	    sm->state[i].u.context.cur_lookup = SFD_ParseNestedLookup(sfd,sf,old);
	} else if ( sm->type == asm_insert ) {
	    getint(sfd,&temp);
	    if ( temp==0 )
		sm->state[i].u.insert.mark_ins = NULL;
	    else {
		sm->state[i].u.insert.mark_ins = malloc(temp+1); sm->state[i].u.insert.mark_ins[temp] = '\0';
		nlgetc(sfd);	/* skip space */
		fread(sm->state[i].u.insert.mark_ins,1,temp,sfd);
	    }
	    getint(sfd,&temp);
	    if ( temp==0 )
		sm->state[i].u.insert.cur_ins = NULL;
	    else {
		sm->state[i].u.insert.cur_ins = malloc(temp+1); sm->state[i].u.insert.cur_ins[temp] = '\0';
		nlgetc(sfd);	/* skip space */
		fread(sm->state[i].u.insert.cur_ins,1,temp,sfd);
	    }
	} else if ( sm->type == asm_kern ) {
	    int j;
	    getint(sfd,&sm->state[i].u.kern.kcnt);
	    if ( sm->state[i].u.kern.kcnt!=0 )
		sm->state[i].u.kern.kerns = malloc(sm->state[i].u.kern.kcnt*sizeof(int16));
	    for ( j=0; j<sm->state[i].u.kern.kcnt; ++j ) {
		getint(sfd,&temp);
		sm->state[i].u.kern.kerns[j] = temp;
	    }
	}
    }
    getname(sfd,tok);			/* EndASM */
}


static void SFDDumpBitmapChar(FILE *sfd,BDFChar *bfc, int enc,int *newgids) {
    struct enc85 encrypt;
    int i;

    fprintf(sfd, "BDFChar: %d %d %d %d %d %d %d",
	    newgids!=NULL ? newgids[bfc->orig_pos] : bfc->orig_pos, enc,
	    bfc->width, bfc->xmin, bfc->xmax, bfc->ymin, bfc->ymax );
    if ( bfc->sc->parent->hasvmetrics )
	fprintf(sfd, " %d", bfc->vwidth);
    putc('\n',sfd);
    memset(&encrypt,'\0',sizeof(encrypt));
    encrypt.sfd = sfd;
    for ( i=0; i<=bfc->ymax-bfc->ymin; ++i ) {
	uint8 *pt = (uint8 *) (bfc->bitmap + i*bfc->bytes_per_line);
	uint8 *end = pt + bfc->bytes_per_line;
	while ( pt<end ) {
	    SFDEnc85(&encrypt,*pt);
	    ++pt;
	}
    }
    SFDEnc85EndEnc(&encrypt);
    fputc('\n',sfd);
}


const char *EncName(Encoding *encname) {
return( encname->enc_name );
}


int getname(FILE *sfd, char *tokbuf) {
    int ch;

    while ( isspace(ch = nlgetc(sfd)));
    ungetc(ch,sfd);
return( getprotectedname(sfd,tokbuf));
}


static void MMInferStuff(MMSet *mm) {
    int i,j;

    if ( mm==NULL )
return;
    if ( mm->apple ) {
	for ( i=0; i<mm->axis_count; ++i ) {
	    for ( j=0; j<mm->axismaps[i].points; ++j ) {
		real val = mm->axismaps[i].blends[j];
		if ( val == -1. )
		    mm->axismaps[i].min = mm->axismaps[i].designs[j];
		else if ( val==0 )
		    mm->axismaps[i].def = mm->axismaps[i].designs[j];
		else if ( val==1 )
		    mm->axismaps[i].max = mm->axismaps[i].designs[j];
	    }
	}
    }
}


char **NamesReadSFD(char *filename) {
    FILE *sfd = fopen(filename,"r");
    char tok[2000];
    char **ret = NULL;
    int eof;

    if ( sfd==NULL )
return( NULL );
    locale_t tmplocale; locale_t oldlocale; // Declare temporary locale storage.
    switch_to_c_locale(&tmplocale, &oldlocale); // Switch to the C locale temporarily and cache the old locale.
    if ( SFDStartsCorrectly(sfd,tok)!=-1 ) {
	while ( !feof(sfd)) {
	    if ( (eof = getname(sfd,tok))!=1 ) {
		if ( eof==-1 )
	break;
		geteol(sfd,tok);
	continue;
	    }
	    if ( strmatch(tok,"FontName:")==0 ) {
		getname(sfd,tok);
		ret = malloc(2*sizeof(char*));
		ret[0] = copy(tok);
		ret[1] = NULL;
	break;
	    }
	}
    }
    switch_to_old_locale(&tmplocale, &oldlocale); // Switch to the cached locale.
    fclose(sfd);
return( ret );
}


static void SFDEnc85(struct enc85 *enc,int ch) {
    enc->sofar[enc->pos++] = ch;
    if ( enc->pos==4 ) {
	unsigned int val = (enc->sofar[0]<<24)|(enc->sofar[1]<<16)|(enc->sofar[2]<<8)|enc->sofar[3];
	if ( val==0 ) {
	    fputc('z',enc->sfd);
	    ++enc->ccnt;
	} else {
	    int ch2, ch3, ch4, ch5;
	    ch5 = val%85;
	    val /= 85;
	    ch4 = val%85;
	    val /= 85;
	    ch3 = val%85;
	    val /= 85;
	    ch2 = val%85;
	    val /= 85;
	    fputc('!'+val,enc->sfd);
	    fputc('!'+ch2,enc->sfd);
	    fputc('!'+ch3,enc->sfd);
	    fputc('!'+ch4,enc->sfd);
	    fputc('!'+ch5,enc->sfd);
	    enc->ccnt += 5;
	    if ( enc->ccnt > 70 ) { fputc('\n',enc->sfd); enc->ccnt=0; }
	}
	enc->pos = 0;
    }
}


static void SFDDumpGlyphVariants(FILE *sfd,struct glyphvariants *gv,const char *name) {
    int i;

    if ( gv==NULL )
return;

    if ( gv->variants!=NULL )
	fprintf( sfd, "GlyphVariants%s: %s\n", name, gv->variants );
    if ( gv->part_cnt!=0 ) {
	if ( gv->italic_correction!=0 ) {
	    fprintf( sfd, "GlyphComposition%sIC: %d", name, gv->italic_correction );
	    if ( gv->italic_adjusts!=NULL ) {
		putc(' ',sfd);
		SFDDumpDeviceTable(sfd,gv->italic_adjusts);
	    }
	    putc('\n',sfd);
	}
	fprintf( sfd, "GlyphComposition%s: %d ", name, gv->part_cnt );
	for ( i=0; i<gv->part_cnt; ++i ) {
	    fprintf( sfd, " %s%%%d,%d,%d,%d", gv->parts[i].component,
		    gv->parts[i].is_extender,
		    gv->parts[i].startConnectorLength,
		    gv->parts[i].endConnectorLength,
		    gv->parts[i].fullAdvance);
	}
	putc('\n',sfd);
    }
}


SplineFont *_SFDRead(char *filename,FILE *sfd) {
return( SFD_Read(filename,sfd,false));
}


void SFDDumpCharStartingMarker(FILE *sfd,SplineChar *sc) {
    if ( AllAscii(sc->name))
	fprintf(sfd, "StartChar: %s\n", sc->name );
    else {
	fprintf(sfd, "StartChar: " );
	SFDDumpUTF7Str(sfd,sc->name);
	putc('\n',sfd);
    }
}


static void SFDDumpPattern(FILE *sfd, const char *keyword, struct pattern *pattern) {

    fprintf( sfd, "%s %s %g;%g [%g %g %g %g %g %g]\n", keyword,
	    pattern->pattern,
	    (double) pattern->width, (double) pattern->height,
	    (double) pattern->transform[0], (double) pattern->transform[1],
	    (double) pattern->transform[2], (double) pattern->transform[3],
	    (double) pattern->transform[4], (double) pattern->transform[5] );
}


static struct lookup_subtable *CreateSubtable(OTLookup *otl,SplineFont1 *sf) {
    struct lookup_subtable *cur, *prev;

    cur = chunkalloc(sizeof(struct lookup_subtable));
    if ( otl->subtables==NULL )
	otl->subtables = cur;
    else {
	for ( prev=otl->subtables; prev->next!=NULL; prev=prev->next );
	prev->next = cur;
    }
    cur->lookup = otl;
    if ( otl->lookup_type == gsub_single ||
	    otl->lookup_type == gsub_multiple ||
	    otl->lookup_type == gsub_alternate ||
	    otl->lookup_type == gsub_ligature ||
	    otl->lookup_type == gpos_single ||
	    otl->lookup_type == gpos_pair )
	cur->per_glyph_pst_or_kern = true;
    else if ( otl->lookup_type == gpos_cursive ||
	    otl->lookup_type == gpos_mark2base ||
	    otl->lookup_type == gpos_mark2ligature ||
	    otl->lookup_type == gpos_mark2mark )
	cur->anchor_classes = true;
    if ( otl->lookup_type == gpos_pair ) {
	if ( otl->features!=NULL &&
		otl->features->featuretag==CHR('v','k','r','n'))
	    cur->vertical_kerning = true;
    }
return( cur );
}


static void SFDParseJustify(FILE *sfd, SplineFont *sf, char *tok) {
    Justify *last=NULL, *cur;
    struct jstf_lang *jlang, *llast;
    int p = 0,ch;

    while ( strcmp(tok,"Justify:")==0 ) {
	cur = chunkalloc(sizeof(Justify));
	if ( last==NULL )
	    sf->justify = cur;
	else
	    last->next = cur;
	last = cur;
	llast = jlang = NULL;
	cur->script = gettag(sfd);
	while ( getname(sfd,tok)>0 ) {
	    if ( strcmp(tok,"Justify:")==0 || strcmp(tok,"EndJustify")==0 )
	break;
	    if ( strcmp(tok,"JstfExtender:")==0 ) {
		while ( (ch=nlgetc(sfd))==' ' );
		ungetc(ch,sfd);
		geteol(sfd,tok);
		cur->extenders = copy(tok);
	    } else if ( strcmp(tok,"JstfLang:")==0 ) {
		jlang = chunkalloc(sizeof(struct jstf_lang));
		if ( llast==NULL )
		    cur->langs = jlang;
		else
		    llast->next = jlang;
		llast = jlang;
		jlang->lang = gettag(sfd);
		p = -1;
		getint(sfd,&jlang->cnt);
		if ( jlang->cnt!=0 )
		    jlang->prios = calloc(jlang->cnt,sizeof(struct jstf_prio));
	    } else if ( strcmp(tok,"JstfPrio:")==0 ) {
		if ( jlang!=NULL ) {
		    ++p;
		    if ( p>= jlang->cnt ) {
			jlang->prios = realloc(jlang->prios,(p+1)*sizeof(struct jstf_prio));
			memset(jlang->prios+jlang->cnt,0,(p+1-jlang->cnt)*sizeof(struct jstf_prio));
			jlang->cnt = p+1;
		    }
		}
	    } else if ( strcmp(tok,"JstfEnableShrink:" )==0 ) {
		if ( p<0 ) p=0;
		if ( jlang!=NULL && p<jlang->cnt )
		    jlang->prios[p].enableShrink = SFDLookupList(sfd,sf);
	    } else if ( strcmp(tok,"JstfDisableShrink:" )==0 ) {
		if ( p<0 ) p=0;
		if ( jlang!=NULL && p<jlang->cnt )
		    jlang->prios[p].disableShrink = SFDLookupList(sfd,sf);
	    } else if ( strcmp(tok,"JstfMaxShrink:" )==0 ) {
		if ( p<0 ) p=0;
		if ( jlang!=NULL && p<jlang->cnt )
		    jlang->prios[p].maxShrink = SFDLookupList(sfd,sf);
	    } else if ( strcmp(tok,"JstfEnableExtend:" )==0 ) {
		if ( p<0 ) p=0;
		if ( jlang!=NULL && p<jlang->cnt )
		    jlang->prios[p].enableExtend = SFDLookupList(sfd,sf);
	    } else if ( strcmp(tok,"JstfDisableExtend:" )==0 ) {
		if ( p<0 ) p=0;
		if ( jlang!=NULL && p<jlang->cnt )
		    jlang->prios[p].disableExtend = SFDLookupList(sfd,sf);
	    } else if ( strcmp(tok,"JstfMaxExtend:" )==0 ) {
		if ( p<0 ) p=0;
		if ( jlang!=NULL && p<jlang->cnt )
		    jlang->prios[p].maxExtend = SFDLookupList(sfd,sf);
	    } else
		geteol(sfd,tok);
	}
    }
}


static int GSubOrder(SplineFont1 *sf,FeatureScriptLangList *fl) {
    struct table_ordering *ord;
    int sofar = 30000, temp;

    for ( ord=sf->orders; ord!=NULL && ord->table_tag!=CHR('G','S','U','B');
	    ord = ord->next );
    for ( ; fl!=NULL; fl=fl->next ) {
	temp = TTFFeatureIndex(fl->featuretag,ord);
	if ( temp<sofar )
	    sofar = temp;
    }
return( sofar );
}


void SFD_DumpLookup( FILE *sfd, SplineFont *sf ) {
    int isgpos;
    OTLookup *otl;
    struct lookup_subtable *sub;
    FeatureScriptLangList *fl;
    struct scriptlanglist *sl;
    int i;

    for ( isgpos=0; isgpos<2; ++isgpos ) {
	for ( otl = isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl = otl->next ) {
	    fprintf( sfd, "Lookup: %d %d %d ", otl->lookup_type, otl->lookup_flags, otl->store_in_afm );
	    SFDDumpUTF7Str(sfd,otl->lookup_name);
	    fprintf( sfd, " { " );
	    for ( sub=otl->subtables; sub!=NULL; sub=sub->next ) {
		SFDDumpUTF7Str(sfd,sub->subtable_name);
		putc(' ',sfd);
		if ( otl->lookup_type==gsub_single && sub->suffix!=NULL ) {
		    putc('(',sfd);
		    SFDDumpUTF7Str(sfd,sub->suffix);
		    putc(')',sfd);
		} else if ( otl->lookup_type==gpos_pair && sub->vertical_kerning )
		    fprintf(sfd,"(1)");
		if ( otl->lookup_type==gpos_pair && (sub->separation!=0 || sub->kerning_by_touch))
		    fprintf(sfd,"[%d,%d,%d]", sub->separation, sub->minkern, sub->kerning_by_touch+2*sub->onlyCloser+4*sub->dontautokern );
		putc(' ',sfd);
	    }
	    fprintf( sfd, "} [" );
	    for ( fl=otl->features; fl!=NULL; fl=fl->next ) {
		if ( fl->ismac )
		    fprintf( sfd, "<%d,%d> (",
			    (int) (fl->featuretag>>16),
			    (int) (fl->featuretag&0xffff));
		else
		    fprintf( sfd, "'%c%c%c%c' (",
			    (int) (fl->featuretag>>24), (int) ((fl->featuretag>>16)&0xff),
			    (int) ((fl->featuretag>>8)&0xff), (int) (fl->featuretag&0xff) );
		for ( sl= fl->scripts; sl!=NULL; sl = sl->next ) {
		    fprintf( sfd, "'%c%c%c%c' <",
			    (int) (sl->script>>24), (int) ((sl->script>>16)&0xff),
			    (int) ((sl->script>>8)&0xff), (int) (sl->script&0xff) );
		    for ( i=0; i<sl->lang_cnt; ++i ) {
			uint32 lang = i<MAX_LANG ? sl->langs[i] : sl->morelangs[i-MAX_LANG];
			fprintf( sfd, "'%c%c%c%c' ",
				(int) (lang>>24), (int) ((lang>>16)&0xff),
				(int) ((lang>>8)&0xff), (int) (lang&0xff) );
		    }
		    fprintf( sfd, "> " );
		}
		fprintf( sfd, ") " );
	    }
	    fprintf( sfd, "]\n" );
	}
    }
}


static void SFDDumpTtfTable(FILE *sfd,struct ttf_table *tab,SplineFont *sf) {
    if ( tab->tag == CHR('p','r','e','p') || tab->tag == CHR('f','p','g','m') ) {
	/* These are tables of instructions and should be dumped as such */
	char *instrs;
	char *pt;
	fprintf( sfd, "TtTable: %c%c%c%c\n",
		(int) (tab->tag>>24), (int) ((tab->tag>>16)&0xff), (int) ((tab->tag>>8)&0xff), (int) (tab->tag&0xff) );
	instrs = _IVUnParseInstrs( tab->data,tab->len );
	for ( pt=instrs; *pt!='\0'; ++pt )
	    putc(*pt,sfd);
	if ( pt[-1]!='\n' )
	    putc('\n',sfd);
	free(instrs);
	fprintf( sfd, "%s\n", end_tt_instrs );
    } else if ( (tab->tag == CHR('c','v','t',' ') || tab->tag == CHR('m','a','x','p')) &&
	    (tab->len&1)==0 ) {
	int i, ended;
	uint8 *pt;
	fprintf( sfd, "ShortTable: %c%c%c%c %d\n",
		(int) (tab->tag>>24), (int) ((tab->tag>>16)&0xff), (int) ((tab->tag>>8)&0xff), (int) (tab->tag&0xff),
		(int) (tab->len>>1) );
	pt = (uint8*) tab->data;
	ended = tab->tag!=CHR('c','v','t',' ') || sf->cvt_names==NULL;
	for ( i=0; i<(tab->len>>1); ++i ) {
	    int num = (int16) ((pt[0]<<8) | pt[1]);
	    fprintf( sfd, "  %d", num );
	    if ( !ended ) {
		if ( sf->cvt_names[i]==END_CVT_NAMES )
		    ended=true;
		else if ( sf->cvt_names[i]!=NULL ) {
		    putc(' ',sfd);
		    SFDDumpUTF7Str(sfd,sf->cvt_names[i]);
		    putc(' ',sfd);
		}
	    }
	    putc('\n',sfd);
	    pt += 2;
	}
	fprintf( sfd, "EndShort\n");
    } else {
	/* maxp, who knows what. Dump 'em as binary for now */
	struct enc85 enc;
	int i;

	memset(&enc,'\0',sizeof(enc));
	enc.sfd = sfd;

	fprintf( sfd, "TtfTable: %c%c%c%c %d\n",
		(int) (tab->tag>>24), (int) ((tab->tag>>16)&0xff), (int) ((tab->tag>>8)&0xff), (int) (tab->tag&0xff),
		(int) tab->len );
	for ( i=0; i<tab->len; ++i )
	    SFDEnc85(&enc,tab->data[i]);
	SFDEnc85EndEnc(&enc);
	fprintf(sfd,"\nEndTtf\n" );
    }
}


static int TTFFeatureIndex( uint32 tag, struct table_ordering *ord ) {
    /* This is the order in which features should be executed */
    int cnt = 0;

    if ( ord!=NULL ) {
	for ( cnt=0; ord->ordered_features[cnt]!=0; ++cnt )
	    if ( ord->ordered_features[cnt]==tag )
	break;
return( cnt );
    }

    cnt+=2;

    switch ( tag ) {
/* GSUB ordering */
      case CHR('c','c','m','p'):	/* Must be first? */
return( cnt-2 );
      case CHR('l','o','c','l'):	/* Language dependent letter forms (serbian uses some different glyphs than russian) */
return( cnt-1 );
      case CHR('i','s','o','l'):
return( cnt );
      case CHR('j','a','l','t'):		/* must come after 'isol' */
return( cnt+1 );
      case CHR('f','i','n','a'):
return( cnt+2 );
      case CHR('f','i','n','2'):
      case CHR('f','a','l','t'):		/* must come after 'fina' */
return( cnt+3 );
      case CHR('f','i','n','3'):
return( cnt+4 );
      case CHR('m','e','d','i'):
return( cnt+5 );
      case CHR('m','e','d','2'):
return( cnt+6 );
      case CHR('i','n','i','t'):
return( cnt+7 );

      case CHR('r','t','l','a'):
return( cnt+100 );
      case CHR('s','m','c','p'): case CHR('c','2','s','c'):
return( cnt+200 );

      case CHR('r','l','i','g'):
return( cnt+300 );
      case CHR('c','a','l','t'):
return( cnt+301 );
      case CHR('l','i','g','a'):
return( cnt+302 );
      case CHR('d','l','i','g'): case CHR('h','l','i','g'):
return( cnt+303 );
      case CHR('c','s','w','h'):
return( cnt+304 );
      case CHR('m','s','e','t'):
return( cnt+305 );

      case CHR('f','r','a','c'):
return( cnt+306 );

/* Indic processing */
      case CHR('n','u','k','t'):
      case CHR('p','r','e','f'):
return( cnt+301 );
      case CHR('a','k','h','n'):
return( cnt+302 );
      case CHR('r','p','h','f'):
return( cnt+303 );
      case CHR('b','l','w','f'):
return( cnt+304 );
      case CHR('h','a','l','f'):
      case CHR('a','b','v','f'):
return( cnt+305 );
      case CHR('p','s','t','f'):
return( cnt+306 );
      case CHR('v','a','t','u'):
return( cnt+307 );

      case CHR('p','r','e','s'):
return( cnt+310 );
      case CHR('b','l','w','s'):
return( cnt+311 );
      case CHR('a','b','v','s'):
return( cnt+312 );
      case CHR('p','s','t','s'):
return( cnt+313 );
      case CHR('c','l','i','g'):
return( cnt+314 );

      case CHR('h','a','l','n'):
return( cnt+320 );
/* end indic ordering */

      case CHR('a','f','r','c'):
      case CHR('l','j','m','o'):
      case CHR('v','j','m','o'):
return( cnt+350 );
      case CHR('v','r','t','2'): case CHR('v','e','r','t'):
return( cnt+1010 );		/* Documented to come last */

/* Unknown things come after everything but vert/vrt2 */
      default:
return( cnt+1000 );

    }
}


char *utf7toutf8_copy(const char *_str) {
    char *buffer = NULL, *pt, *end = NULL;
    int ch1, ch2, ch3, ch4, done;
    int prev_cnt=0, prev=0, in=0;
    const char *str = _str;

    if ( str==NULL )
return( NULL );
    buffer = pt = malloc(400);
    end = pt+400;
    while ( (ch1=*str++)!='\0' ) {
	done = 0;
	if ( !done && !in ) {
	    if ( ch1=='+' ) {
		ch1=*str++;
		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[(unsigned char) *str++];
		if ( ch2==1 ) {
		    --str;
		    ch2 = ch3 = ch4 = 0;
		} else {
		    ch3 = inbase64[(unsigned char) *str++];
		    if ( ch3==-1 ) {
			--str;
			ch3 = ch4 = 0;
		    } else {
			ch4 = inbase64[(unsigned char) *str++];
			if ( ch4==-1 ) {
			    --str;
			    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 ) {
	    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 );
	}
    }
    *pt = '\0';
    pt = copy(buffer);
    free(buffer );
return( pt );
}


static int SFDGetBitmapChar(FILE *sfd,BDFFont *bdf) {
    BDFChar *bfc;
    struct enc85 dec;
    int i, enc, orig;
    int width,xmax,xmin,ymax,ymin, vwidth=-1;
    EncMap *map;
    int ch;

    map = bdf->sf->map;

    if ( getint(sfd,&orig)!=1 || orig<0 )
return( 0 );
    if ( getint(sfd,&enc)!=1 )
return( 0 );
    if ( getint(sfd,&width)!=1 )
return( 0 );
    if ( getint(sfd,&xmin)!=1 )
return( 0 );
    if ( getint(sfd,&xmax)!=1 )
return( 0 );
    if ( getint(sfd,&ymin)!=1 )
return( 0 );
    while ( (ch=nlgetc(sfd))==' ');
    ungetc(ch,sfd);
    if ( ch=='\n' || ch=='\r' || getint(sfd,&ymax)!=1 ) {
	/* Old style format, no orig_pos given, shift everything by 1 */
	ymax = ymin;
	ymin = xmax;
	xmax = xmin;
	xmin = width;
	width = enc;
	enc = orig;
	orig = map->map[enc];
    } else {
	while ( (ch=nlgetc(sfd))==' ');
	ungetc(ch,sfd);
	if ( ch!='\n' && ch!='\r' )
	    getint(sfd,&vwidth);
    }
    if ( enc<0 ||xmax<xmin || ymax<ymin )
return( 0 );

    bfc = chunkalloc(sizeof(BDFChar));
    if (bfc == NULL)
        return 0;

    if ( orig==-1 ) {
	bfc->sc = SFMakeChar(bdf->sf,map,enc);
	orig = bfc->sc->orig_pos;
    }

    bfc->orig_pos = orig;
    bfc->width = width;
    bfc->ymax = ymax; bfc->ymin = ymin;
    bfc->xmax = xmax; bfc->xmin = xmin;
    bdf->glyphs[orig] = bfc;
    bfc->sc = bdf->sf->glyphs[orig];
    bfc->vwidth = vwidth!=-1 ? vwidth :
	    rint(bfc->sc->vwidth*bdf->pixelsize / (real) (bdf->sf->ascent+bdf->sf->descent));
    if ( bdf->clut==NULL ) {
	bfc->bytes_per_line = (bfc->xmax-bfc->xmin)/8 +1;
	bfc->depth = 1;
    } else {
	bfc->bytes_per_line = bfc->xmax-bfc->xmin +1;
	bfc->byte_data = true;
	bfc->depth = bdf->clut->clut_len==4 ? 2 : bdf->clut->clut_len==16 ? 4 : 8;
    }
    bfc->bitmap = calloc((bfc->ymax-bfc->ymin+1)*bfc->bytes_per_line,sizeof(uint8));

    memset(&dec,'\0', sizeof(dec)); dec.pos = -1;
    dec.sfd = sfd;
    for ( i=0; i<=bfc->ymax-bfc->ymin; ++i ) {
	uint8 *pt = (uint8 *) (bfc->bitmap + i*bfc->bytes_per_line);
	uint8 *end = (uint8 *) (bfc->bitmap + (i+1)*bfc->bytes_per_line);
	while ( pt<end ) {
	    *pt++ = Dec85(&dec);
	}
    }
    if ( bfc->sc==NULL ) {
	bdf->glyphs[bfc->orig_pos] = NULL;
	BDFCharFree(bfc);
    }
/* This fixes a bug: We didn't set "widthset" on splinechars when reading in */
/*  winfonts. We should set it now on any bitmaps worth outputting to make up*/
/*  for that. Eventually we should have good sfd files and can remove this */
    else if ( bfc->sc->width!=bdf->sf->ascent + bdf->sf->descent )
	bfc->sc->widthset = true;
return( 1 );
}


char *utf8toutf7_copy(const char *_str) {
    int ch, prev_cnt=0, prev=0, in=0;
    const unsigned char *str = (const unsigned char *) _str;
    int i, len;
    char *ret=NULL, *ostr=NULL;

    if ( str==NULL )
return( NULL );
    for ( i=0; i<2; ++i ) {
	str = (const unsigned char *) _str;
	len= prev_cnt= prev= in=0;
	while ( (ch = *str++)!='\0' ) {
	    /* Convert from utf8 to ucs2 */
	    if ( ch<=127 )
		/* Done */;
	    else if ( ch<=0xdf && *str!='\0' ) {
		ch = ((ch&0x1f)<<6) | (*str++&0x3f);
	    } else if ( ch<=0xef && *str!='\0' && str[1]!='\0' ) {
		ch = ((ch&0xf)<<12) | ((str[0]&0x3f)<<6) | (str[1]&0x3f);
		str += 2;
	    } else if ( *str!='\0' && str[1]!='\0' && str[2]!='\0' ) {
		int w = ( ((ch&0x7)<<2) | ((str[0]&0x30)>>4) )-1;
		int s1, s2;
		s1 = (w<<6) | ((str[0]&0xf)<<2) | ((str[1]&0x30)>>4);
		s2 = ((str[1]&0xf)<<6) | (str[2]&0x3f);
		ch = (s1*0x400)+s2 + 0x10000;
		str += 3;
	    } else {
		/* illegal */
	    }
	    if ( ch<127 && ch!='\n' && ch!='\r' && ch!='\\' && ch!='~' &&
		    ch!='+' && ch!='=' && ch!='"' ) {
		if ( prev_cnt!=0 ) {
		    if ( i ) {
			prev<<= (prev_cnt==1?16:8);
			ostr = base64_encode(ostr,prev);
			prev_cnt=prev=0;
		    } else
			len += 4;
		}
		if ( in ) {
		    if ( inbase64[ch]!=-1 || ch=='-' ) {
			if ( i )
			    *ostr++ = '-';
			else
			    ++len;
		    }
		    in = 0;
		}
		if ( i )
		    *ostr++ = ch;
		else
		    ++len;
	    } else if ( ch=='+' && !in ) {
		if ( i ) {
		    *ostr++ = '+';
		    *ostr++ = '-';
		} else
		    len += 2;
	    } else if ( prev_cnt== 0 ) {
		if ( !in ) {
		    if ( i )
			*ostr++ = '+';
		    else
			++len;
		    in = 1;
		}
		prev = ch;
		prev_cnt = 2;		/* 2 bytes */
	    } else if ( prev_cnt==2 ) {
		prev<<=8;
		prev += (ch>>8)&0xff;
		if ( i ) {
		    ostr = base64_encode(ostr,prev);
		    prev_cnt=prev=0;
		} else
		    len += 4;
		prev = (ch&0xff);
		prev_cnt=1;
	    } else {
		prev<<=16;
		prev |= ch;
		if ( i ) {
		    ostr = base64_encode(ostr,prev);
		    prev_cnt=prev=0;
		} else
		    len += 4;
		prev_cnt = prev = 0;
	    }
	}
	if ( prev_cnt==2 ) {
	    prev<<=8;
	    if ( i ) {
		ostr = base64_encode(ostr,prev);
		prev_cnt=prev=0;
	    } else
		len += 4;
	} else if ( prev_cnt==1 ) {
	    prev<<=16;
	    if ( i ) {
		ostr = base64_encode(ostr,prev);
		prev_cnt=prev=0;
	    } else
		len += 4;
	}
	if ( in ) {
	    if ( i )
		*ostr++ = '-';
	    else
		++len;
	}
	if ( i==0 )
	    ostr = ret = malloc(len+1);
    }
    *ostr = '\0';
return( ret );
}


static struct macname *SFDParseMacNames(FILE *sfd, char *tok) {
    struct macname *head=NULL, *last=NULL, *cur;
    int enc, lang, len;
    char *pt;
    int ch;

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

	getint(sfd,&enc);
	getint(sfd,&lang);
	getint(sfd,&len);
	cur->enc = enc;
	cur->lang = lang;
	cur->name = pt = malloc(len+1);

	while ( (ch=nlgetc(sfd))==' ');
	if ( ch=='"' )
	    ch = nlgetc(sfd);
	while ( ch!='"' && ch!=EOF && pt<cur->name+len ) {
	    if ( ch=='\\' ) {
		*pt  = (nlgetc(sfd)-'0')<<6;
		*pt |= (nlgetc(sfd)-'0')<<3;
		*pt |= (nlgetc(sfd)-'0');
	    } else
		*pt++ = ch;
	    ch = nlgetc(sfd);
	}
	*pt = '\0';
	getname(sfd,tok);
    }
return( head );
}


enum uni_interp interp_from_encoding(Encoding *enc,enum uni_interp interp) {

    if ( enc==NULL )
return( interp );

    if ( enc->is_japanese )
	interp = ui_japanese;
    else if ( enc->is_korean )
	interp = ui_korean;
    else if ( enc->is_tradchinese )
	interp = ui_trad_chinese;
    else if ( enc->is_simplechinese )
	interp = ui_simp_chinese;
return( interp );
}


static struct glyphvariants *SFDParseGlyphComposition(FILE *sfd,
	struct glyphvariants *gv, char *tok) {
    int i;

    if ( gv==NULL )
	gv = chunkalloc(sizeof(struct glyphvariants));
    getint(sfd,&gv->part_cnt);
    gv->parts = calloc(gv->part_cnt,sizeof(struct gv_part));
    for ( i=0; i<gv->part_cnt; ++i ) {
	int temp, ch;
	getname(sfd,tok);
	gv->parts[i].component = copy(tok);
	while ( (ch=nlgetc(sfd))==' ' );
	if ( ch!='%' ) ungetc(ch,sfd);
	getint(sfd,&temp);
	gv->parts[i].is_extender = temp;
	while ( (ch=nlgetc(sfd))==' ' );
	if ( ch!=',' ) ungetc(ch,sfd);
	getint(sfd,&temp);
	gv->parts[i].startConnectorLength=temp;
	while ( (ch=nlgetc(sfd))==' ' );
	if ( ch!=',' ) ungetc(ch,sfd);
	getint(sfd,&temp);
	gv->parts[i].endConnectorLength = temp;
	while ( (ch=nlgetc(sfd))==' ' );
	if ( ch!=',' ) ungetc(ch,sfd);
	getint(sfd,&temp);
	gv->parts[i].fullAdvance = temp;
    }
return( gv );
}


static int SLContains(struct script_record *sr, uint32 script, uint32 lang) {
    int i, j;

    if ( script==DEFAULT_SCRIPT || script == 0 )
return( true );
    for ( i=0; sr[i].script!=0; ++i ) {
	if ( sr[i].script==script ) {
	    if ( lang==0 )
return( true );
	    for ( j=0; sr[i].langs[j]!=0; ++j )
		if ( sr[i].langs[j]==lang )
return( true );

return( false );	/* this script entry didn't contain the language. won't be any other scripts to check */
	}
    }
return( false );	/* Never found script */
}


static void utf7_encode(FILE *sfd,long ch) {

    putc(base64[(ch>>18)&0x3f],sfd);
    putc(base64[(ch>>12)&0x3f],sfd);
    putc(base64[(ch>>6)&0x3f],sfd);
    putc(base64[ch&0x3f],sfd);
}


static void SFDDumpMacName(FILE *sfd,struct macname *mn) {
    char *pt;

    while ( mn!=NULL ) {
      fprintf( sfd, "MacName: %d %d %d \"", mn->enc, mn->lang,
	       (int)strlen(mn->name) );
	for ( pt=mn->name; *pt; ++pt ) {
	    if ( *pt<' ' || *pt>=0x7f || *pt=='\\' || *pt=='"' )
		fprintf( sfd, "\\%03o", *(uint8 *) pt );
	    else
		putc(*pt,sfd);
	}
	fprintf( sfd, "\"\n" );
	mn = mn->next;
    }
}


static SplineFont *SFD_Read(char *filename,FILE *sfd, int fromdir) {
    SplineFont *sf=NULL;
    char tok[2000];
    double version;

    if ( sfd==NULL ) {
	if ( fromdir ) {
	    snprintf(tok,sizeof(tok),"%s/" FONT_PROPS, filename );
	    sfd = fopen(tok,"r");
	} else
	    sfd = fopen(filename,"r");
    }
    if ( sfd==NULL )
return( NULL );
    locale_t tmplocale; locale_t oldlocale; // Declare temporary locale storage.
    switch_to_c_locale(&tmplocale, &oldlocale); // Switch to the C locale temporarily and cache the old locale.
    ff_progress_change_stages(2);
    if ( (version = SFDStartsCorrectly(sfd,tok))!=-1 )
	sf = SFD_GetFont(sfd,NULL,tok,fromdir,filename,version);
    switch_to_old_locale(&tmplocale, &oldlocale); // Switch to the cached locale.
    if ( sf!=NULL ) {
	sf->filename = copy(filename);
	if ( sf->mm!=NULL ) {
	    int i;
	    for ( i=0; i<sf->mm->instance_count; ++i )
		sf->mm->instances[i]->filename = copy(filename);
	} else if ( !sf->onlybitmaps ) {
/* Jonathyn Bet'nct points out that once you edit in an outline window, even */
/*  if by mistake, your onlybitmaps status is gone for good */
/* Regenerate it if the font has no splines, refs, etc. */
	    int i;
	    SplineChar *sc;
	    for ( i=sf->glyphcnt-1; i>=0; --i )
		if ( (sc = sf->glyphs[i])!=NULL &&
			(sc->layer_cnt!=2 ||
			 sc->layers[ly_fore].splines!=NULL ||
			 sc->layers[ly_fore].refs!=NULL ))
	     break;
	     if ( i==-1 )
		 sf->onlybitmaps = true;
	}
    }
    fclose(sfd);
return( sf );
}


static void SFDGetTtfInstrs(FILE *sfd, SplineChar *sc) {
    /* We've read the TtfInstr token, it is followed by a byte count */
    /* and then the instructions in enc85 format */
    int i,len;
    struct enc85 dec;

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

    getint(sfd,&len);
    sc->ttf_instrs = malloc(len);
    sc->ttf_instrs_len = len;
    for ( i=0; i<len; ++i )
	sc->ttf_instrs[i] = Dec85(&dec);
}


static int ask_about_file(char *filename, int *state, FILE **asfd) {
    int ret;
    char *buts[6];
    char buffer[800], *pt;

    if ((*asfd = fopen(filename, "r")) == NULL) {
        return false;
    } else if (*state&1) { //Recover all
        return true;
    } else if (*state&2) { //Forget all
        fclose(*asfd);
        *asfd = NULL;
        unlink(filename);
        return false;
    }

    fgets(buffer,sizeof(buffer),*asfd);
    rewind(*asfd);
    if (strncmp(buffer,"Base: ",6) != 0) {
        strcpy(buffer+6, "<New File>");
    }
    pt = buffer+6;
    if (strlen(buffer+6) > 70) {
        pt = strrchr(buffer+6,'/');
        if (pt == NULL)
            pt = buffer+6;
    }

    buts[0] = _("Yes"); buts[1] = _("Yes to _All");
    buts[2] = _("_Skip for now");
    buts[3] = _("Forget _to All"); buts[4] = _("_Forget about it");
    buts[5] = NULL;
    ret = ff_ask(_("Recover old edit"),(const char **) buts,0,3,_("You appear to have an old editing session on %s.\nWould you like to recover it?"), pt);
    switch (ret) {
        case 1: //Recover all
            *state = 1;
            break;
        case 2: //Skip one
            fclose(*asfd);
            *asfd = NULL;
            return false;
        case 3: //Forget all
            *state = 2;
            /* Fall through */
        case 4: //Forget one
            fclose(*asfd);
            *asfd = NULL;
            unlink(filename);
            return false;
        default: //Recover one
            break;
    }
    return true;
}


static void SFDDumpCharMath(FILE *sfd,SplineChar *sc) {
    if ( sc->italic_correction!=TEX_UNDEF && sc->italic_correction!=0 ) {
	fprintf( sfd, "ItalicCorrection: %d", sc->italic_correction );
	if ( sc->italic_adjusts!=NULL ) {
	    putc(' ',sfd);
	    SFDDumpDeviceTable(sfd,sc->italic_adjusts);
	}
	putc('\n',sfd);
    }
    if ( sc->top_accent_horiz!=TEX_UNDEF ) {
	fprintf( sfd, "TopAccentHorizontal: %d", sc->top_accent_horiz );
	if ( sc->top_accent_adjusts!=NULL ) {
	    putc(' ',sfd);
	    SFDDumpDeviceTable(sfd,sc->top_accent_adjusts);
	}
	putc('\n',sfd);
    }
    if ( sc->is_extended_shape )
	fprintf( sfd, "IsExtendedShape: %d\n", sc->is_extended_shape );
    SFDDumpGlyphVariants(sfd,sc->vert_variants,"Vertical");
    SFDDumpGlyphVariants(sfd,sc->horiz_variants,"Horizontal");
    if ( sc->mathkern!=NULL ) {
	SFDDumpMathVertex(sfd,&sc->mathkern->top_right,"TopRightVertex:");
	SFDDumpMathVertex(sfd,&sc->mathkern->top_left,"TopLeftVertex:");
	SFDDumpMathVertex(sfd,&sc->mathkern->bottom_right,"BottomRightVertex:");
	SFDDumpMathVertex(sfd,&sc->mathkern->bottom_left,"BottomLeftVertex:");
    }
}


static OTLookup *CreateLookup(SplineFont1 *sf,uint32 tag, int sli,
	int flags,enum possub_type type) {
    OTLookup *otl = chunkalloc(sizeof(OTLookup));

    otl->lookup_type =
	    type == pst_position ? gpos_single :
	    type == pst_pair ? gpos_pair :
	    type == pst_contextpos ? gpos_context :
	    type == pst_chainpos ? gpos_contextchain :
	    type == pst_substitution ? gsub_single :
	    type == pst_alternate ? gsub_alternate :
	    type == pst_multiple ? gsub_multiple :
	    type == pst_ligature ? gsub_ligature :
	    type == pst_contextsub ? gsub_context :
	    type == pst_chainsub ? gsub_contextchain :
		ot_undef;
    if ( otl->lookup_type == ot_undef )
	IError("Unknown lookup type");
    if ( otl->lookup_type<gpos_single ) {
	otl->next = sf->sf.gsub_lookups;
	sf->sf.gsub_lookups = otl;
    } else {
	otl->next = sf->sf.gpos_lookups;
	sf->sf.gpos_lookups = otl;
    }
    otl->lookup_flags = flags;
    otl->features = FeaturesFromTagSli(tag,sli,sf);
    /* We will set the lookup_index after we've ordered the list */
    /* We will set the lookup_name after we've assigned the index */
    /* We will add subtables as we need them */
return( otl );
}


int SFAddScriptIndex(SplineFont1 *sf,uint32 *scripts,int scnt) {
    int i,j;
    struct script_record *sr;

    if ( scnt==0 )
	scripts[scnt++] = CHR('l','a','t','n');		/* Need a default script preference */
    for ( i=0; i<scnt-1; ++i ) for ( j=i+1; j<scnt; ++j ) {
	if ( scripts[i]>scripts[j] ) {
	    uint32 temp = scripts[i];
	    scripts[i] = scripts[j];
	    scripts[j] = temp;
	}
    }

    if ( sf->sf.cidmaster ) sf = (SplineFont1 *) sf->sf.cidmaster;
    if ( sf->script_lang==NULL )	/* It's an old sfd file */
	sf->script_lang = calloc(1,sizeof(struct script_record *));
    for ( i=0; sf->script_lang[i]!=NULL; ++i ) {
	sr = sf->script_lang[i];
	for ( j=0; sr[j].script!=0 && j<scnt &&
		sr[j].script==scripts[j]; ++j );
	if ( sr[j].script==0 && j==scnt )
return( i );
    }

    sf->script_lang = realloc(sf->script_lang,(i+2)*sizeof(struct script_record *));
    sf->script_lang[i+1] = NULL;
    sr = sf->script_lang[i] = calloc(scnt+1,sizeof(struct script_record));
    for ( j = 0; j<scnt; ++j ) {
	sr[j].script = scripts[j];
	sr[j].langs = malloc(2*sizeof(uint32));
	sr[j].langs[0] = DEFAULT_LANG;
	sr[j].langs[1] = 0;
    }
return( i );
}


static int nlgetc(FILE *sfd) {
    int ch, ch2;

    ch=getc(sfd);
    if ( ch!='\\' )
return( ch );
    ch2 = getc(sfd);
    if ( ch2=='\n' )
return( nlgetc(sfd));
    ungetc(ch2,sfd);
return( ch );
}


static void SFDDumpChar(FILE *sfd,SplineChar *sc,EncMap *map,int *newgids,int todir,int saveUndoes) {
    // TODO: Output the U. F. O. glif name.
    ImageList *img;
    KernPair *kp;
    PST *pst;
    int i, v, enc;
    struct altuni *altuni;

    if (!todir)
	putc('\n',sfd);

    SFDDumpCharStartingMarker( sfd, sc );
    if ( (enc = map->backmap[sc->orig_pos])>=map->enccount ) {
	if ( sc->parent->cidmaster==NULL )
	    IError("Bad reverse encoding");
	enc = -1;
    }
    if ( sc->unicodeenc!=-1 &&
	    ((map->enc->is_unicodebmp && sc->unicodeenc<0x10000) ||
	     (map->enc->is_unicodefull && sc->unicodeenc < (int)unicode4_size)) )
	/* If we have altunis, then the backmap may not give the primary */
	/*  unicode code point, which is what we need here */
	fprintf(sfd, "Encoding: %d %d %d\n", sc->unicodeenc, sc->unicodeenc,
		newgids!=NULL?newgids[sc->orig_pos]:sc->orig_pos);
    else
	fprintf(sfd, "Encoding: %d %d %d\n", enc, sc->unicodeenc,
		newgids!=NULL?newgids[sc->orig_pos]:sc->orig_pos);
    if ( sc->altuni ) {
	fprintf( sfd, "AltUni2:" );
	for ( altuni = sc->altuni; altuni!=NULL; altuni=altuni->next )
	    fprintf( sfd, " %06x.%06x.%x", altuni->unienc, altuni->vs, altuni->fid );
	putc( '\n', sfd);
    }
    if ( sc->glif_name ) {
        fprintf(sfd, "GlifName: ");
        if ( AllAscii(sc->glif_name))
	    fprintf(sfd, "%s", sc->glif_name );
        else {
	    SFDDumpUTF7Str(sfd,sc->glif_name);
        }
        putc('\n',sfd);
    }
    fprintf(sfd, "Width: %d\n", sc->width );
    if ( sc->vwidth!=sc->parent->ascent+sc->parent->descent )
	fprintf(sfd, "VWidth: %d\n", sc->vwidth );
    if ( sc->glyph_class!=0 )
	fprintf(sfd, "GlyphClass: %d\n", sc->glyph_class );
    if ( sc->unlink_rm_ovrlp_save_undo )
	fprintf(sfd, "UnlinkRmOvrlpSave: %d\n", sc->unlink_rm_ovrlp_save_undo );
    if ( sc->inspiro )
	fprintf(sfd, "InSpiro: %d\n", sc->inspiro );
    if ( sc->lig_caret_cnt_fixed )
	fprintf(sfd, "LigCaretCntFixed: %d\n", sc->lig_caret_cnt_fixed );
    if ( sc->changedsincelasthinted|| sc->manualhints || sc->widthset )
	fprintf(sfd, "Flags: %s%s%s%s%s\n",
		sc->changedsincelasthinted?"H":"",
		sc->manualhints?"M":"",
		sc->widthset?"W":"",
		sc->views!=NULL?"O":"",
		sc->instructions_out_of_date?"I":"");
    if ( sc->tex_height!=TEX_UNDEF || sc->tex_depth!=TEX_UNDEF )
	fprintf( sfd, "TeX: %d %d\n", sc->tex_height, sc->tex_depth );
    if ( sc->is_extended_shape || sc->italic_correction!=TEX_UNDEF ||
	    sc->top_accent_horiz!=TEX_UNDEF || sc->vert_variants!=NULL ||
	    sc->horiz_variants!=NULL || sc->mathkern!=NULL )
	SFDDumpCharMath(sfd,sc);
#if HANYANG
    if ( sc->compositionunit )
	fprintf( sfd, "CompositionUnit: %d %d\n", sc->jamo, sc->varient );
#endif
    SFDDumpHintList(sfd,"HStem: ", sc->hstem);
    SFDDumpHintList(sfd,"VStem: ", sc->vstem);
    SFDDumpDHintList(sfd,"DStem2: ", sc->dstem);
    if ( sc->countermask_cnt!=0 ) {
	fprintf( sfd, "CounterMasks: %d", sc->countermask_cnt );
	for ( i=0; i<sc->countermask_cnt; ++i ) {
	    putc(' ',sfd);
	    SFDDumpHintMask(sfd,&sc->countermasks[i]);
	}
	putc('\n',sfd);
    }
    if ( sc->ttf_instrs_len!=0 )
	SFDDumpTtfInstrs(sfd,sc);
    SFDDumpAnchorPoints(sfd,sc->anchor);
    fprintf( sfd, "LayerCount: %d\n", sc->layer_cnt );
    for ( i=0; i<sc->layer_cnt; ++i ) {
        if( saveUndoes && UndoRedoLimitToSave > 0) {
            if( sc->layers[i].undoes || sc->layers[i].redoes ) {
                fprintf(sfd, "UndoRedoHistory\n" );
                fprintf(sfd, "Layer: %d\n", i );
                Undoes *undo = 0;
                int idx   = 0;
                int limit = 0;

                fprintf(sfd, "Undoes\n" );
                idx = 0;
                undo = sc->layers[i].undoes;
                for( limit = UndoRedoLimitToSave;
                     undo && (limit==-1 || limit>0);
                     undo = undo->next, idx++ ) {
                    SFDDumpUndo( sfd, sc, undo, "Undo", idx );
                    if( limit > 0 )
                        limit--;
                }
                fprintf(sfd, "EndUndoes\n" );

		fprintf(sfd, "Redoes\n" );
                idx = 0;
                limit = UndoRedoLimitToSave;
                undo = sc->layers[i].redoes;
                for( limit = UndoRedoLimitToSave;
                     undo && (limit==-1 || limit>0);
                     undo = undo->next, idx++ ) {
                    SFDDumpUndo( sfd, sc, undo, "Redo", idx );
                    if( limit > 0 )
                        limit--;
                }
                fprintf(sfd, "EndRedoes\n" );
                fprintf(sfd, "EndUndoRedoHistory\n" );
            }
        }

	if ( sc->parent->multilayer ) {
	    fprintf(sfd, "Layer: %d  %d %d %d  #%06x %g  #%06x %g %g %s %s [%g %g %g %g] [",
		    i, sc->layers[i].dofill, sc->layers[i].dostroke, sc->layers[i].fillfirst,
		    sc->layers[i].fill_brush.col, (double) sc->layers[i].fill_brush.opacity,
		    sc->layers[i].stroke_pen.brush.col, (double) sc->layers[i].stroke_pen.brush.opacity,
		    (double) sc->layers[i].stroke_pen.width, joins[sc->layers[i].stroke_pen.linejoin], caps[sc->layers[i].stroke_pen.linecap],
		    (double) sc->layers[i].stroke_pen.trans[0], (double) sc->layers[i].stroke_pen.trans[1],
		    (double) sc->layers[i].stroke_pen.trans[2], (double) sc->layers[i].stroke_pen.trans[3] );
	    if ( sc->layers[i].stroke_pen.dashes[0]==0 && sc->layers[i].stroke_pen.dashes[1]==DASH_INHERITED )
		fprintf(sfd,"0 %d]\n", DASH_INHERITED);
	    else { int j;
		for ( j=0; j<DASH_MAX && sc->layers[i].stroke_pen.dashes[j]!=0; ++j )
		    fprintf( sfd,"%d ", sc->layers[i].stroke_pen.dashes[j]);
		fprintf(sfd,"]\n");
	    }
	    if ( sc->layers[i].fill_brush.gradient!=NULL )
		SFDDumpGradient(sfd,"FillGradient:", sc->layers[i].fill_brush.gradient );
	    else if ( sc->layers[i].fill_brush.pattern!=NULL )
		SFDDumpPattern(sfd,"FillPattern:", sc->layers[i].fill_brush.pattern );
	    if ( sc->layers[i].stroke_pen.brush.gradient!=NULL )
		SFDDumpGradient(sfd,"StrokeGradient:", sc->layers[i].stroke_pen.brush.gradient );
	    else if ( sc->layers[i].stroke_pen.brush.pattern!=NULL )
		SFDDumpPattern(sfd,"StrokePattern:", sc->layers[i].stroke_pen.brush.pattern );
	} else {
	    if ( sc->layers[i].images==NULL && sc->layers[i].splines==NULL &&
		    sc->layers[i].refs==NULL && (sc->layers[i].validation_state&vs_known) == 0 &&
		    sc->layers[i].python_persistent == NULL)
    continue;
	    if ( i==ly_back )
		fprintf( sfd, "Back\n" );
	    else if ( i==ly_fore )
		fprintf( sfd, "Fore\n" );
	    else
		fprintf(sfd, "Layer: %d\n", i );
	}
	for ( img=sc->layers[i].images; img!=NULL; img=img->next )
#ifndef _NO_LIBPNG
        if (WritePNGInSFD)
	    SFDDumpImagePNG(sfd,img);
        else
#endif
	    SFDDumpImage(sfd,img);
	if ( sc->layers[i].splines!=NULL ) {
	    fprintf(sfd, "SplineSet\n" );
	    SFDDumpSplineSet(sfd,sc->layers[i].splines,sc->layers[i].order2);
	}
	SFDDumpRefs(sfd,sc->layers[i].refs,newgids);
	SFDDumpGuidelines(sfd, sc->layers[i].guidelines);
	if ( sc->layers[i].validation_state&vs_known )
	    fprintf( sfd, "Validated: %d\n", sc->layers[i].validation_state );
        if ( sc->layers[i].python_persistent!=NULL )
	  SFDPickleMe(sfd,sc->layers[i].python_persistent,sc->layers[i].python_persistent_has_lists);
    }
    for ( v=0; v<2; ++v ) {
	kp = v ? sc->vkerns : sc->kerns;
	if ( kp!=NULL ) {
	    fprintf( sfd, v ? "VKerns2:" : "Kerns2:" );
	    for ( ; kp!=NULL; kp=kp->next ) {
            if ( !SFDOmit(kp->sc)) {
                fprintf( sfd, " %d %d ",
                         newgids!=NULL?newgids[kp->sc->orig_pos]:kp->sc->orig_pos,
                         kp->off );
                SFDDumpUTF7Str(sfd,kp->subtable->subtable_name);
                if ( kp->adjust!=NULL ) putc(' ',sfd);
                SFDDumpDeviceTable(sfd,kp->adjust);
            }
        }
	    fprintf(sfd, "\n" );
	}
    }
    for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
	if (( pst->subtable==NULL && pst->type!=pst_lcaret) || pst->type==pst_null )
	    /* Skip it */;
	else {
	    static const char *keywords[] = { "Null:", "Position2:", "PairPos2:",
		    "Substitution2:",
		    "AlternateSubs2:", "MultipleSubs2:", "Ligature2:",
		    "LCarets2:", NULL };
	    fprintf( sfd, "%s ", keywords[pst->type] );
	    if ( pst->subtable!=NULL ) {
		SFDDumpUTF7Str(sfd,pst->subtable->subtable_name);
		putc(' ',sfd);
	    }
	    if ( pst->type==pst_position ) {
		fprintf( sfd, "dx=%d dy=%d dh=%d dv=%d",
			pst->u.pos.xoff, pst->u.pos.yoff,
			pst->u.pos.h_adv_off, pst->u.pos.v_adv_off);
		SFDDumpValDevTab(sfd,pst->u.pos.adjust);
		putc('\n',sfd);
	    } else if ( pst->type==pst_pair ) {
		fprintf( sfd, "%s dx=%d dy=%d dh=%d dv=%d",
			pst->u.pair.paired,
			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 );
		SFDDumpValDevTab(sfd,pst->u.pair.vr[0].adjust);
		fprintf( sfd, " dx=%d dy=%d dh=%d dv=%d",
			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);
		SFDDumpValDevTab(sfd,pst->u.pair.vr[1].adjust);
		putc('\n',sfd);
	    } else if ( pst->type==pst_lcaret ) {
		int i;
		fprintf( sfd, "%d ", pst->u.lcaret.cnt );
		for ( i=0; i<pst->u.lcaret.cnt; ++i ) {
		    fprintf( sfd, "%d", pst->u.lcaret.carets[i] );
                    if ( i<pst->u.lcaret.cnt-1 ) putc(' ',sfd);
                }
		fprintf( sfd, "\n" );
	    } else
		fprintf( sfd, "%s\n", pst->u.lig.components );
	}
    }
    if ( sc->comment!=NULL ) {
	fprintf( sfd, "Comment: " );
	SFDDumpUTF7Str(sfd,sc->comment);
	putc('\n',sfd);
    }
    if ( sc->user_decomp != NULL ) {
	fprintf( sfd, "Decomposition: " );
	char* temp_ud = u2utf8_copy(sc->user_decomp);
	SFDDumpUTF7Str(sfd, temp_ud);
	free(temp_ud);
	putc('\n',sfd);
    }
    if ( sc->color!=COLOR_DEFAULT )
	fprintf( sfd, "Colour: %x\n", (int) sc->color );
    if ( sc->parent->multilayer ) {
	if ( sc->tile_margin!=0 )
	    fprintf( sfd, "TileMargin: %g\n", (double) sc->tile_margin );
	else if ( sc->tile_bounds.minx!=0 || sc->tile_bounds.maxx!=0 )
	    fprintf( sfd, "TileBounds: %g %g %g %g\n", (double) sc->tile_bounds.minx, (double) sc->tile_bounds.miny, (double) sc->tile_bounds.maxx, (double) sc->tile_bounds.maxy );
    }
    fprintf(sfd,"EndChar\n" );
}


static void SFDDumpJSTFLookups(FILE *sfd,const char *keyword, OTLookup **list ) {
    int i;

    if ( list==NULL || list[0]==NULL )
return;

    fprintf( sfd, "%s ", keyword );
    for ( i=0; list[i]!=NULL; ++i ) {
	SFDDumpUTF7Str(sfd,list[i]->lookup_name);
	if ( list[i+1]!=NULL ) putc(' ',sfd);
    }
    putc('\n',sfd);
}


static void SFDGetMinimumDistances(FILE *sfd, SplineChar *sc) {
    SplineSet *ss;
    SplinePoint *sp;
    int pt,i, val, err;
    int ch;
    SplinePoint **mapping=NULL;
    MinimumDistance *last, *md, *mdhead=NULL;

    for ( i=0; i<2; ++i ) {
	pt = 0;
	for ( ss = sc->layers[ly_fore].splines; ss!=NULL; ss=ss->next ) {
	    for ( sp=ss->first; ; ) {
		if ( mapping!=NULL ) mapping[pt] = sp;
		pt++;
		if ( sp->next == NULL )
	    break;
		sp = sp->next->to;
		if ( sp==ss->first )
	    break;
	    }
	}
	if ( mapping==NULL )
	    mapping = calloc(pt,sizeof(SplinePoint *));
    }

    last = NULL;
    for ( ch=nlgetc(sfd); ch!=EOF && ch!='\n'; ch=nlgetc(sfd)) {
	err = false;
	while ( isspace(ch) && ch!='\n' ) ch=nlgetc(sfd);
	if ( ch=='\n' )
    break;
	md = chunkalloc(sizeof(MinimumDistance));
	if ( ch=='x' ) md->x = true;
	getint(sfd,&val);
	if ( val<-1 || val>=pt ) {
	    IError( "Minimum Distance specifies bad point (%d) in sfd file\n", val );
	    err = true;
	} else if ( val!=-1 ) {
	    md->sp1 = mapping[val];
	    md->sp1->dontinterpolate = true;
	}
	ch = nlgetc(sfd);
	if ( ch!=',' ) {
	    IError( "Minimum Distance lacks a comma where expected\n" );
	    err = true;
	}
	getint(sfd,&val);
	if ( val<-1 || val>=pt ) {
	    IError( "Minimum Distance specifies bad point (%d) in sfd file\n", val );
	    err = true;
	} else if ( val!=-1 ) {
	    md->sp2 = mapping[val];
	    md->sp2->dontinterpolate = true;
	}
	if ( !err ) {
	    if ( last!=NULL )
		last->next = md;
	    last = md;
	} else
	    chunkfree(md,sizeof(MinimumDistance));
    }
    free(mapping);

    /* Obsolete concept */
    MinimumDistancesFree(mdhead);
}


static SplineChar *SCDuplicate(SplineChar *sc) {
    SplineChar *matched = sc;

    if ( sc==NULL || sc->parent==NULL || sc->parent->cidmaster!=NULL )
return( sc );		/* Can't do this in CID keyed fonts */

    if ( sc->layer_cnt!=2 )
return( sc );

    while ( sc->layers[ly_fore].refs!=NULL &&
	    sc->layers[ly_fore].refs->sc!=NULL &&	/* Can happen if we are called during font loading before references are fixed up */
	    sc->layers[ly_fore].refs->next==NULL &&
	    sc->layers[ly_fore].refs->transform[0]==1 && sc->layers[ly_fore].refs->transform[1]==0 &&
	    sc->layers[ly_fore].refs->transform[2]==0 && sc->layers[ly_fore].refs->transform[3]==1 &&
	    sc->layers[ly_fore].refs->transform[4]==0 && sc->layers[ly_fore].refs->transform[5]==0 ) {
	char *basename = sc->layers[ly_fore].refs->sc->name;
	if ( strcmp(sc->name,basename)!=0 )
    break;
	matched = sc->layers[ly_fore].refs->sc;
	sc = sc->layers[ly_fore].refs->sc;
    }
return( matched );
}


int SFDWriteBakExtended(char* locfilename,
			SplineFont *sf,EncMap *map,EncMap *normal,
			int s2d,
			int localRevisionsToRetain )
{
    int rc = 0;

    if( s2d )
    {
	rc = SFDWrite(locfilename,sf,map,normal,s2d);
	return rc;
    }

    int cacheRevisionsToRetain = prefRevisionsToRetain;

    sf->save_to_dir = s2d;

    if( localRevisionsToRetain < 0 )
    {
	// If there are no backups, then don't start creating any
	if( !SFDDoesAnyBackupExist(sf->filename))
	    prefRevisionsToRetain = 0;
    }
    else
    {
	prefRevisionsToRetain = localRevisionsToRetain;
    }

    rc = SFDWriteBak( locfilename, sf, map, normal );

    prefRevisionsToRetain = cacheRevisionsToRetain;

    return rc;
}


static void SFDDumpEncoding(FILE *sfd,Encoding *encname,const char *keyword) {
    fprintf(sfd, "%s: %s\n", keyword, encname->enc_name );
}


static int SFDGetBitmapProps(FILE *sfd,BDFFont *bdf,char *tok) {
    int pcnt;
    int i;

    if ( getint(sfd,&pcnt)!=1 || pcnt<=0 )
return( 0 );
    bdf->prop_cnt = pcnt;
    bdf->props = malloc(pcnt*sizeof(BDFProperties));
    for ( i=0; i<pcnt; ++i ) {
	if ( getname(sfd,tok)!=1 )
    break;
	if ( strcmp(tok,"BDFEndProperties")==0 )
    break;
	bdf->props[i].name = copy(tok);
	getint(sfd,&bdf->props[i].type);
	switch ( bdf->props[i].type&~prt_property ) {
	  case prt_int: case prt_uint:
	    getint(sfd,&bdf->props[i].u.val);
	  break;
	  case prt_string: case prt_atom:
	    geteol(sfd,tok);
	    if ( tok[strlen(tok)-1]=='"' ) tok[strlen(tok)-1] = '\0';
	    bdf->props[i].u.str = copy(tok[0]=='"'?tok+1:tok);
	  break;
	  default:
	  break;
	}
    }
    bdf->prop_cnt = i;
return( 1 );
}


static void SFDGetPrivate(FILE *sfd,SplineFont *sf) {
    int i, cnt, len;
    char name[200];
    char *pt, *end;

    sf->private = calloc(1,sizeof(struct psdict));
    getint(sfd,&cnt);
    sf->private->next = sf->private->cnt = cnt;
    sf->private->values = calloc(cnt,sizeof(char *));
    sf->private->keys = calloc(cnt,sizeof(char *));
    for ( i=0; i<cnt; ++i ) {
	getname(sfd,name);
	sf->private->keys[i] = copy(name);
	getint(sfd,&len);
	nlgetc(sfd);	/* skip space */
	pt = sf->private->values[i] = malloc(len+1);
	for ( end = pt+len; pt<end; ++pt )
	    *pt = nlgetc(sfd);
	*pt='\0';
    }
}


static DeviceTable *SFDReadDeviceTable(FILE *sfd,DeviceTable *adjust) {
    int i, junk, first, last, ch, len;

    while ( (ch=nlgetc(sfd))==' ' );
    if ( ch=='{' ) {
	while ( (ch=nlgetc(sfd))==' ' );
	if ( ch=='}' )
return(NULL);
	else
	    ungetc(ch,sfd);
	getint(sfd,&first);
	ch = nlgetc(sfd);		/* Should be '-' */
	getint(sfd,&last);
	len = last-first+1;
	if ( len<=0 ) {
	    IError( "Bad device table, invalid length.\n" );
return(NULL);
	}
	if ( adjust==NULL )
	    adjust = chunkalloc(sizeof(DeviceTable));
	adjust->first_pixel_size = first;
	adjust->last_pixel_size = last;
	adjust->corrections = malloc(len);
	for ( i=0; i<len; ++i ) {
	    while ( (ch=nlgetc(sfd))==' ' );
	    if ( ch!=',' ) ungetc(ch,sfd);
	    getint(sfd,&junk);
	    adjust->corrections[i] = junk;
	}
	while ( (ch=nlgetc(sfd))==' ' );
	if ( ch!='}' ) ungetc(ch,sfd);
    } else
	ungetc(ch,sfd);
return( adjust );
}


static char *base64_encode(char *ostr, long ch) {

    *ostr++ = base64[(ch>>18)&0x3f];
    *ostr++ = base64[(ch>>12)&0x3f];
    *ostr++ = base64[(ch>> 6)&0x3f];
    *ostr++ = base64[(ch    )&0x3f];
return( ostr );
}


static void SFDParseMathItem(FILE *sfd,SplineFont *sf,char *tok) {
    /* The first five characters of a math item's keyword will be "MATH:" */
    /*  the rest will be one of the entries in math_constants_descriptor */
    int i;
    struct MATH *math;

    if ( (math = sf->MATH) == NULL )
	math = sf->MATH = calloc(1,sizeof(struct MATH));
    for ( i=0; math_constants_descriptor[i].script_name!=NULL; ++i ) {
	char *name = math_constants_descriptor[i].script_name;
	int len = strlen( name );
	if ( strncmp(tok+5,name,len)==0 && tok[5+len] == ':' && tok[6+len]=='\0' ) {
	    int16 *pos = (int16 *) (((char *) (math)) + math_constants_descriptor[i].offset );
	    getsint(sfd,pos);
	    if ( math_constants_descriptor[i].devtab_offset != -1 ) {
		DeviceTable **devtab = (DeviceTable **) (((char *) (math)) + math_constants_descriptor[i].devtab_offset );
		*devtab = SFDReadDeviceTable(sfd,*devtab);
    break;
	    }
	}
    }
}


static void ACHasBaseLig(SplineFont1 *sf,AnchorClass1 *ac) {
    int gid,k;
    SplineFont1 *subsf;
    SplineChar *sc;
    AnchorPoint *ap;

    ac->has_bases = ac->has_ligatures = false;
    if ( ac->ac.type==act_mkmk || ac->ac.type==act_curs )
return;
    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_basechar ) {
		    ac->has_bases = true;
		    if ( ac->has_ligatures )
return;
		} else if ( ap->type==at_baselig ) {
		    ac->has_ligatures = true;
		    if ( ac->has_bases )
return;
		}
	    }
	}
	++k;
    } while ( k<sf->sf.subfontcnt );
}


int SFDWrite(char *filename,SplineFont *sf,EncMap *map,EncMap *normal,int todir) {
    FILE *sfd;
    int i, gc;
    char *tempfilename = filename;
    int err = false;

    if ( todir ) {
	SFDirClean(filename);
	GFileMkDir(filename, 0755);		/* this will fail if directory already exists. That's ok */
	tempfilename = malloc(strlen(filename)+strlen("/" FONT_PROPS)+1);
	strcpy(tempfilename,filename); strcat(tempfilename,"/" FONT_PROPS);
    }

    sfd = fopen(tempfilename,"w");
    if ( tempfilename!=filename ) free(tempfilename);
    if ( sfd==NULL )
return( 0 );

    locale_t tmplocale; locale_t oldlocale; // Declare temporary locale storage.
    switch_to_c_locale(&tmplocale, &oldlocale); // Switch to the C locale temporarily and cache the old locale.
    if ( sf->cidmaster!=NULL ) {
	sf=sf->cidmaster;
	gc = 1;
	for ( i=0; i<sf->subfontcnt; ++i )
	    if ( sf->subfonts[i]->glyphcnt > gc )
		gc = sf->subfonts[i]->glyphcnt;
	map = EncMap1to1(gc);
	err = SFDDump(sfd,sf,map,NULL,todir,filename);
	EncMapFree(map);
    } else
	err = SFDDump(sfd,sf,map,normal,todir,filename);
    switch_to_old_locale(&tmplocale, &oldlocale); // Switch to the cached locale.
    if ( ferror(sfd) ) err = true;
    if ( fclose(sfd) ) err = true;
    if ( todir )
	SFFinalDirClean(filename);
return( !err );
}