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);
}

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

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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


static 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);
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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

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

static const char *end_tt_instrs = "EndTTInstrs";

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

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

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

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

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

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

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


static 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 );
}

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

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

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

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

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

#define FONT_PROPS	"font.props"


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 const char *joins[] = { "miter", "round", "bevel", "inher", NULL };

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

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

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

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


static 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 );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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

#define FONT_PROPS	"font.props"

#define STRIKE_PROPS	"strike.props"


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 );
}

/* No explicit global definitions found. */


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 );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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

#define FONT_PROPS	"font.props"


void SFD_AssignLookups(SplineFont1 *sf) {
    PST1 *pst, *pst2;
    int isv;
    KernPair1 *kp, *kp2;
    KernClass1 *kc, *kc2;
    FPST1 *fpst;
    ASM1 *sm;
    AnchorClass1 *ac, *ac2;
    int gid, gid2, cnt, i, k, isgpos;
    SplineFont1 *subsf;
    SplineChar *sc, *sc2;
    OTLookup *otl, **all;
    struct lookup_subtable *sub;

    /* Fix up some gunk from really old versions of the sfd format */
    SFDCleanupAnchorClasses(&sf->sf);
    if ( sf->sf.uni_interp==ui_unset && sf->sf.map!=NULL )
	sf->sf.uni_interp = interp_from_encoding(sf->sf.map->enc,ui_none);

    /* Fixup for an old bug */
    if ( sf->sf.pfminfo.os2_winascent < sf->sf.ascent/4 && !sf->sf.pfminfo.winascent_add ) {
	sf->sf.pfminfo.winascent_add = true;
	sf->sf.pfminfo.os2_winascent = 0;
	sf->sf.pfminfo.windescent_add = true;
	sf->sf.pfminfo.os2_windescent = 0;
    }

    /* First handle the PSTs, no complications here */
    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 ( pst = (PST1 *) (sc->possub); pst!=NULL; pst = (PST1*) (pst->pst.next) ) {
		if ( pst->pst.type == pst_lcaret || pst->pst.subtable!=NULL )
	    continue;		/* Nothing to do, or already done */
		otl = CreateLookup(sf,pst->tag,pst->script_lang_index,pst->flags,pst->pst.type);
		sub = CreateSubtable(otl,sf);
		/* There might be another PST with the same flags on this glyph */
		/* And we must fixup the current pst */
		for ( pst2=pst ; pst2!=NULL; pst2 = (PST1 *) (pst2->pst.next) ) {
		    if ( pst2->tag==pst->tag &&
			    pst2->script_lang_index==pst->script_lang_index &&
			    pst2->flags==pst->flags &&
			    pst2->pst.type==pst->pst.type )
			pst2->pst.subtable = sub;
		}
		for ( gid2=gid+1; gid2<subsf->sf.glyphcnt; ++gid2 ) if ( (sc2=subsf->sf.glyphs[gid2])!=NULL ) {
		    for ( pst2 = (PST1 *) (sc2->possub); pst2!=NULL; pst2 = (PST1 *) (pst2->pst.next) ) {
			if ( pst2->tag==pst->tag &&
				pst2->script_lang_index==pst->script_lang_index &&
				pst2->flags==pst->flags &&
				pst2->pst.type==pst->pst.type )
			    pst2->pst.subtable = sub;
		    }
		}
	    }
	}
	++k;
    } while ( k<sf->sf.subfontcnt );

	/* Now kerns. May need to merge kernclasses to kernpair lookups (different subtables, of course */
    for ( isv=0; isv<2; ++isv ) {
	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 ( kp = (KernPair1 *) (isv ? sc->vkerns : sc->kerns); kp!=NULL; kp = (KernPair1 *) (kp->kp.next) ) {
		    if ( kp->kp.subtable!=NULL )
		continue;		/* already done */
		    otl = CreateLookup(sf,isv ? CHR('v','k','r','n') : CHR('k','e','r','n'),
			    kp->sli,kp->flags,pst_pair);
		    sub = CreateSubtable(otl,sf);
		    /* There might be another kp with the same flags on this glyph */
		    /* And we must fixup the current kp */
		    for ( kp2=kp ; kp2!=NULL; kp2 = (KernPair1 *) (kp2->kp.next) ) {
			if ( kp2->sli==kp->sli && kp2->flags==kp->flags )
			    kp2->kp.subtable = sub;
		    }
		    for ( gid2=gid+1; gid2<subsf->sf.glyphcnt; ++gid2 ) if ( (sc2=subsf->sf.glyphs[gid2])!=NULL ) {
			for ( kp2 = (KernPair1 *) (isv ? sc2->vkerns : sc2->kerns); kp2!=NULL; kp2 = (KernPair1 *) (kp2->kp.next) ) {
			    if ( kp2->sli==kp->sli && kp2->flags==kp->flags )
				kp2->kp.subtable = sub;
			}
		    }
		    /* And there might be a kerning class... */
		    for ( kc=(KernClass1 *) (isv ? sf->sf.vkerns : sf->sf.kerns); kc!=NULL;
			    kc = (KernClass1 *) (kc->kc.next) ) {
			if ( kc->sli == kp->sli && kc->flags == kp->flags && kc->kc.subtable==NULL) {
			    sub = CreateSubtable(otl,sf);
			    sub->per_glyph_pst_or_kern = false;
			    sub->kc = &kc->kc;
			    kc->kc.subtable = sub;
			}
		    }
		}
	    }
	    ++k;
	} while ( k<sf->sf.subfontcnt );
	/* Or there might be a kerning class all by its lonesome */
	for ( kc=(KernClass1 *) (isv ? sf->sf.vkerns : sf->sf.kerns); kc!=NULL;
		kc = (KernClass1 *) (kc->kc.next) ) {
	    if ( kc->kc.subtable==NULL) {
		otl = CreateLookup(sf,isv ? CHR('v','k','r','n') : CHR('k','e','r','n'),
			kc->sli,kc->flags,pst_pair);
		for ( kc2=kc; kc2!=NULL; kc2=(KernClass1 *) (kc2->kc.next) ) {
		    if ( kc->sli == kc2->sli && kc->flags == kc2->flags && kc2->kc.subtable==NULL) {
			sub = CreateSubtable(otl,sf);
			sub->per_glyph_pst_or_kern = false;
			sub->kc = &kc2->kc;
			kc2->kc.subtable = sub;
		    }
		}
	    }
	}
    }

    /* Every FPST and ASM lives in its own lookup with one subtable */
    /* But the old format refered to nested lookups by tag, and now we refer */
    /*  to the lookup itself, so fix that up */
    for ( fpst=(FPST1 *) sf->sf.possub; fpst!=NULL; fpst=((FPST1 *) fpst->fpst.next) ) {
	otl = CreateLookup(sf,fpst->tag, fpst->script_lang_index,
		fpst->flags,fpst->fpst.type);
	sub = CreateSubtable(otl,sf);
	sub->per_glyph_pst_or_kern = false;
	sub->fpst = &fpst->fpst;
	fpst->fpst.subtable = sub;
	FPSTReplaceTagsWithLookups(&fpst->fpst,sf);
    }
    for ( sm=(ASM1 *) sf->sf.sm; sm!=NULL; sm=((ASM1 *) sm->sm.next) ) {
	otl = CreateMacLookup(sf,sm);
	sub = CreateSubtable(otl,sf);
	sub->per_glyph_pst_or_kern = false;
	sub->sm = &sm->sm;
	sm->sm.subtable = sub;
	if ( sm->sm.type==asm_context )
	    ASMReplaceTagsWithLookups(&sm->sm,sf);
    }

    /* We retained the old nested feature tags so we could do the above conversion */
    /*  of tag to lookup. Get rid of them now */
    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 ) {
		chunkfree(otl->features,sizeof(FeatureScriptLangList));
		otl->features = NULL;
	    }
	}
    }

    /* Anchor classes are complicated, because I foolishly failed to distinguish */
    /*  between mark to base and mark to ligature classes. So one AC might have */
    /*  both. If so we need to turn it into two ACs, and have separate lookups */
    /*  for each */
    for ( ac=(AnchorClass1 *) (sf->sf.anchor); ac!=NULL; ac=(AnchorClass1 *) ac->ac.next ) {
	ACHasBaseLig(sf,ac);
	if ( ac->has_ligatures && !ac->has_bases )
	    ac->ac.type = act_mklg;
	else if ( ac->has_ligatures && ac->has_bases )
	    ACDisassociateLigatures(sf,ac);
    }
    for ( ac=(AnchorClass1 *) (sf->sf.anchor); ac!=NULL; ac=(AnchorClass1 *) ac->ac.next ) {
	if ( ac->ac.subtable==NULL ) {
	    otl = CreateACLookup(sf,ac);
	    sub = CreateSubtable(otl,sf);
	    for ( ac2=ac; ac2!=NULL; ac2 = (AnchorClass1 *) ac2->ac.next ) {
		if ( ac2->feature_tag == ac->feature_tag &&
			ac2->script_lang_index == ac->script_lang_index &&
			ac2->flags == ac->flags &&
			ac2->ac.type == ac->ac.type &&
			ac2->merge_with == ac->merge_with )
		    ac2->ac.subtable = sub;
	    }
	}
    }

    /* Now I want to order the gsub lookups. I shan't bother with the gpos */
    /*  lookups because I didn't before */
    for ( otl=sf->sf.gsub_lookups, cnt=0; otl!=NULL; otl=otl->next, ++cnt );
    if ( cnt!=0 ) {
	all = malloc(cnt*sizeof(OTLookup *));
	for ( otl=sf->sf.gsub_lookups, cnt=0; otl!=NULL; otl=otl->next, ++cnt ) {
	    all[cnt] = otl;
	    otl->lookup_index = GSubOrder(sf,otl->features);
	}
	qsort(all,cnt,sizeof(OTLookup *),order_lookups);
	sf->sf.gsub_lookups = all[0];
	for ( i=1; i<cnt; ++i )
	    all[i-1]->next = all[i];
	all[cnt-1]->next = NULL;
	free( all );
    }

    for ( isgpos=0; isgpos<2; ++isgpos ) {
	for ( otl = isgpos ? sf->sf.gpos_lookups : sf->sf.gsub_lookups , cnt=0;
		otl!=NULL; otl = otl->next ) {
	    otl->lookup_index = cnt++;
	    NameOTLookup(otl,&sf->sf);
	}
    }
}


static void SFDGetGasp(FILE *sfd,SplineFont *sf) {
    int i;

    getsint(sfd,(int16 *) &sf->gasp_cnt);
    sf->gasp = malloc(sf->gasp_cnt*sizeof(struct gasp));
    for ( i=0; i<sf->gasp_cnt; ++i ) {
	getsint(sfd,(int16 *) &sf->gasp[i].ppem);
	getsint(sfd,(int16 *) &sf->gasp[i].flags);
    }
    getsint(sfd,(int16 *) &sf->gasp_version);
}


static int SFDDumpBitmapFont(FILE *sfd,BDFFont *bdf,EncMap *encm,int *newgids,
	int todir, char *dirname) {
    int i;
    int err = false;
    BDFChar *bc;
    BDFRefChar *ref;

    ff_progress_next_stage();
    if (bdf->foundry)
        fprintf( sfd, "BitmapFont: %d %d %d %d %d %s\n", bdf->pixelsize, bdf->glyphcnt,
                 bdf->ascent, bdf->descent, BDFDepth(bdf), bdf->foundry );
    else
        fprintf( sfd, "BitmapFont: %d %d %d %d %d\n", bdf->pixelsize, bdf->glyphcnt,
                 bdf->ascent, bdf->descent, BDFDepth(bdf) );
    if ( bdf->prop_cnt>0 ) {
	fprintf( sfd, "BDFStartProperties: %d\n", bdf->prop_cnt );
	for ( i=0; i<bdf->prop_cnt; ++i ) {
	    fprintf(sfd,"%s %d ", bdf->props[i].name, bdf->props[i].type );
	    switch ( bdf->props[i].type&~prt_property ) {
	      case prt_int: case prt_uint:
		fprintf(sfd, "%d\n", bdf->props[i].u.val );
	      break;
	      case prt_string: case prt_atom:
		fprintf(sfd, "\"%s\"\n", bdf->props[i].u.str );
	      break;
	      default:
	      break;
	    }
	}
	fprintf( sfd, "BDFEndProperties\n" );
    }
    if ( bdf->res>20 )
	fprintf( sfd, "Resolution: %d\n", bdf->res );
    for ( i=0; i<bdf->glyphcnt; ++i ) {
	if ( bdf->glyphs[i]!=NULL ) {
	    if ( todir ) {
		char *glyphfile = malloc(strlen(dirname)+2*strlen(bdf->glyphs[i]->sc->name)+20);
		FILE *gsfd;
		appendnames(glyphfile,dirname,"/",bdf->glyphs[i]->sc->name,BITMAP_EXT );
		gsfd = fopen(glyphfile,"w");
		if ( gsfd!=NULL ) {
		    SFDDumpBitmapChar(gsfd,bdf->glyphs[i],encm->backmap[i],newgids);
		    if ( ferror(gsfd)) err = true;
		    if ( fclose(gsfd)) err = true;
		} else
		    err = true;
		free(glyphfile);
	    } else
		SFDDumpBitmapChar(sfd,bdf->glyphs[i],encm->backmap[i],newgids);
	}
	ff_progress_next();
    }
    for ( i=0; i<bdf->glyphcnt; ++i ) if (( bc = bdf->glyphs[i] ) != NULL ) {
    	for ( ref=bc->refs; ref!=NULL; ref=ref->next )
	    fprintf(sfd, "BDFRefChar: %d %d %d %d %c\n",
		newgids!=NULL ? newgids[bc->orig_pos] : bc->orig_pos,
		newgids!=NULL ? newgids[ref->bdfc->orig_pos] : ref->bdfc->orig_pos,
		ref->xoff,ref->yoff,ref->selected?'S':'N' );
    }
    fprintf( sfd, "EndBitmapFont\n" );
return( err );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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

#define FONT_PROPS	"font.props"

#define STRIKE_PROPS	"strike.props"

#define BITMAP_EXT	".bitmap"


static void SFDPickleMe(FILE *sfd,void *python_data, int python_data_has_lists) {
    char *string, *pt;

#ifdef _NO_PYTHON
    string = (char *) python_data;
#else
    string = PyFF_PickleMeToString(python_data);
#endif
    if ( string==NULL )
return;
    if (python_data_has_lists)
    fprintf( sfd, "PickledDataWithLists: \"" );
    else
    fprintf( sfd, "PickledData: \"" );
    for ( pt=string; *pt; ++pt ) {
	if ( *pt=='\\' || *pt=='"' )
	    putc('\\',sfd);
	putc(*pt,sfd);
    }
    fprintf( sfd, "\"\n");
#ifndef _NO_PYTHON
    free(string);
#endif
}


static void SFDDumpBase(FILE *sfd,const char *keyword,struct Base *base) {
    int i;
    struct basescript *bs;
    struct baselangextent *bl;

    fprintf( sfd, "%s %d", keyword, base->baseline_cnt );
    for ( i=0; i<base->baseline_cnt; ++i ) {
	fprintf( sfd, " '%c%c%c%c'",
		base->baseline_tags[i]>>24,
		base->baseline_tags[i]>>16,
		base->baseline_tags[i]>>8,
		base->baseline_tags[i]);
    }
    putc('\n',sfd);

    for ( bs=base->scripts; bs!=NULL; bs=bs->next ) {
	fprintf( sfd, "BaseScript: '%c%c%c%c' %d ",
		bs->script>>24, bs->script>>16, bs->script>>8, bs->script,
		bs->def_baseline );
	for ( i=0; i<base->baseline_cnt; ++i )
	    fprintf( sfd, " %d", bs->baseline_pos[i]);
	for ( bl=bs->langs; bl!=NULL; bl=bl->next )
	    SFDDumpBaseLang(sfd,bl);
	putc('\n',sfd);
    }
}


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


static void SFDDumpBaseLang(FILE *sfd,struct baselangextent *bl) {

    if ( bl->lang==0 )
	fprintf( sfd, " { %d %d", bl->descent, bl->ascent );
    else
	fprintf( sfd, " { '%c%c%c%c' %d %d",
		bl->lang>>24, bl->lang>>16, bl->lang>>8, bl->lang,
		bl->descent, bl->ascent );
    for ( bl=bl->features; bl!=NULL; bl=bl->next )
	SFDDumpBaseLang(sfd,bl);
    putc('}',sfd);
}

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

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

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

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

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

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

static const char *end_tt_instrs = "EndTTInstrs";

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

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

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

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

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

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

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

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

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


static PST1 *LigaCreateFromOldStyleMultiple(PST1 *liga) {
    char *pt;
    PST1 *new, *last=liga;
    while ( (pt = strrchr(liga->pst.u.lig.components,';'))!=NULL ) {
	new = chunkalloc(sizeof( PST1 ));
	*new = *liga;
	new->pst.u.lig.components = copy(pt+1);
	last->pst.next = (PST *) new;
	last = new;
	*pt = '\0';
    }
return( last );
}


static int PeekMatch(FILE *stream, const char * target) {
  // This returns 1 if target matches the next characters in the stream.
  int pos1 = 0;
  int lastread = getc(stream);
  while (target[pos1] != '\0' && lastread != EOF && lastread == target[pos1]) {
    pos1 ++; lastread = getc(stream);
  }

  int rewind_amount = pos1 + ((lastread == EOF) ? 0 : 1);
  fseek(stream, -rewind_amount, SEEK_CUR);
  return (target[pos1] == '\0');
}


static void SFDGetTtInstrs(FILE *sfd, SplineChar *sc) {
    /* We've read the TtInstr token, it is followed by text versions of */
    /*  the instructions, slurp it all into a big buffer, and then parse that */
    char *buf=NULL, *pt=buf, *end=buf;
    int ch;
    int backlen = strlen(end_tt_instrs);
    int instr_len;

    while ( (ch=nlgetc(sfd))!=EOF ) {
	if ( pt>=end ) {
	    char *newbuf = realloc(buf,(end-buf+200));
	    pt = newbuf+(pt-buf);
	    end = newbuf+(end+200-buf);
	    buf = newbuf;
	}
	*pt++ = ch;
	if ( pt-buf>backlen && strncmp(pt-backlen,end_tt_instrs,backlen)==0 ) {
	    pt -= backlen;
    break;
	}
    }
    *pt = '\0';

    sc->ttf_instrs = _IVParse(sc->parent,buf,&instr_len,tterr,NULL);
    sc->ttf_instrs_len = instr_len;

    free(buf);
}


static int SFDDump(FILE *sfd,SplineFont *sf,EncMap *map,EncMap *normal,
	int todir, char *dirname) {
    int i, realcnt;
    BDFFont *bdf;
    int err = false;

    realcnt = sf->glyphcnt;
    if ( sf->subfontcnt!=0 ) {
	for ( i=0; i<sf->subfontcnt; ++i )
	    if ( realcnt<sf->subfonts[i]->glyphcnt )
		realcnt = sf->subfonts[i]->glyphcnt;
    }
    for ( i=0, bdf = sf->bitmaps; bdf!=NULL; bdf=bdf->next, ++i );
    ff_progress_start_indicator(10,_("Saving..."),_("Saving Spline Font Database"),_("Saving Outlines"),
	    realcnt,i+1);
    ff_progress_enable_stop(false);
#ifndef _NO_LIBPNG
    double version = 3.2;
    if (!WritePNGInSFD) version = 3.1;
#else
    double version = 3.1;
#endif
    if (!UndoRedoLimitToSave && version == 3.1) {
        version = 3.0;
    }
    fprintf(sfd, "SplineFontDB: %.1f\n", version );
    if ( sf->mm != NULL )
	err = SFD_MMDump(sfd,sf->mm->normal,map,normal,todir,dirname);
    else
	err = SFD_Dump(sfd,sf,map,normal,todir,dirname);
    ff_progress_end_indicator();
return( err );
}


static OTLookup **SFDLookupList(FILE *sfd,SplineFont *sf) {
    int ch;
    OTLookup **ret=NULL, *otl;
    int lcnt=0, lmax=0;
    char *name;

    for (;;) {
	while ( (ch=nlgetc(sfd))==' ' );
	if ( ch=='\n' || ch==EOF )
    break;
	ungetc(ch,sfd);
	name = SFDReadUTF7Str(sfd);
	otl = SFFindLookup(sf,name);
	free(name);
	if ( otl!=NULL ) {
	    if ( lcnt>=lmax ) {
	        lmax += 100;
	        ret = realloc(ret, lmax * sizeof(OTLookup *));
	    }
	    ret[lcnt++] = otl;
	}
    }
    if ( lcnt==0 )
return( NULL );
    ret = realloc(ret, (lcnt+1) * sizeof(OTLookup *));
    ret[lcnt] = NULL;
return( ret );
}


static OTLookup *CreateMacLookup(SplineFont1 *sf,ASM1 *sm) {
    OTLookup *otl = chunkalloc(sizeof(OTLookup));
    int i, ch;
    char *pt, *start;
    SplineChar *sc;

    otl->features = chunkalloc(sizeof(FeatureScriptLangList));
    if ( sm->sm.type == asm_kern ) {
	otl->lookup_type = kern_statemachine;
	otl->next = sf->sf.gpos_lookups;
	sf->sf.gpos_lookups = otl;
	otl->features->featuretag = (sm->sm.flags&0x8000) ? CHR('v','k','r','n') : CHR('k','e','r','n');
    } else {
	otl->lookup_type = sm->sm.type==asm_indic ? morx_indic : sm->sm.type==asm_context ? morx_context : morx_insert;
	otl->next = sf->sf.gsub_lookups;
	sf->sf.gsub_lookups = otl;
	otl->features->featuretag = (sm->feature<<16) | (sm->setting);
	otl->features->ismac = true;
    }
    otl->lookup_flags = 0;

    for ( i=4; i<sm->sm.class_cnt; ++i ) {
	for ( start=sm->sm.classes[i]; ; start = pt ) {
	    while ( *start==' ' ) ++start;
	    if ( *start=='\0' )
	break;
	    for ( pt=start ; *pt!='\0' && *pt!=' '; ++pt );
	    ch = *pt; *pt = '\0';
	    sc = SFGetChar(&sf->sf,-1,start);
	    if ( sc!=NULL )
		FListAppendScriptLang(otl->features,SCScriptFromUnicode(sc),
			DEFAULT_LANG);
	    *pt = ch;
	}
    }

    /* 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 );
}


static void putstring(FILE *sfd, const char *header, char *body) {
    fprintf( sfd, "%s", header );
    while ( *body ) {
	if ( *body=='\n' || *body == '\\' || *body=='\r' ) {
	    putc('\\',sfd);
	    if ( *body=='\\' )
		putc('\\',sfd);
	    else {
		putc('n',sfd);
		if ( *body=='\r' && body[1]=='\n' )
		    ++body;
	    }
	} else
	    putc(*body,sfd);
	++body;
    }
    putc('\n',sfd);
}


void SFClearAutoSave(SplineFont *sf) {
    int i;
    SplineFont *ssf;

    if ( sf->cidmaster!=NULL ) sf = sf->cidmaster;
    sf->changed_since_autosave = false;
    for ( i=0; i<sf->subfontcnt; ++i ) {
	ssf = sf->subfonts[i];
	ssf->changed_since_autosave = false;
	if ( ssf->autosavename!=NULL ) {
	    unlink( ssf->autosavename );
	    free( ssf->autosavename );
	    ssf->autosavename = NULL;
	}
    }
    if ( sf->autosavename==NULL )
return;
    unlink(sf->autosavename);
    free(sf->autosavename);
    sf->autosavename = NULL;
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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

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

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

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

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

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

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

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

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

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

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

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

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


static void SFDParseLookup(FILE *sfd,OTLookup *otl) {
    int ch;
    struct lookup_subtable *sub, *lastsub;
    FeatureScriptLangList *fl, *lastfl;
    struct scriptlanglist *sl, *lastsl;
    int i, lcnt, lmax=0;
    uint32 *langs=NULL;
    char *subname;

    while ( (ch=nlgetc(sfd))==' ' );
    if ( ch=='{' ) {
	lastsub = NULL;
	while ( (subname = SFDReadUTF7Str(sfd))!=NULL ) {
	    while ( (ch=nlgetc(sfd))==' ' );
	    ungetc(ch,sfd);
	    sub = chunkalloc(sizeof(struct lookup_subtable));
	    sub->subtable_name = subname;
	    sub->lookup = otl;
	    switch ( otl->lookup_type ) {
	      case gsub_single:
		while ( (ch=nlgetc(sfd))==' ' );
		if ( ch=='(' ) {
		    sub->suffix = SFDReadUTF7Str(sfd);
		    while ( (ch=nlgetc(sfd))==' ' );
			/* slurp final paren */
		} else
		    ungetc(ch,sfd);
		sub->per_glyph_pst_or_kern = true;
	      break;
	      case gsub_multiple: case gsub_alternate: case gsub_ligature:
	      case gpos_single:
		sub->per_glyph_pst_or_kern = true;
	      break;
	      case gpos_pair:
		if ( (ch=nlgetc(sfd))=='(' ) {
		    ch = nlgetc(sfd);
		    sub->vertical_kerning = (ch=='1');
		    nlgetc(sfd);	/* slurp final paren */
		    ch=nlgetc(sfd);
		}
		if ( ch=='[' ) {
		    getsint(sfd,&sub->separation);
		    nlgetc(sfd);	/* slurp comma */
		    getsint(sfd,&sub->minkern);
		    nlgetc(sfd);	/* slurp comma */
		    ch = nlgetc(sfd);
		    sub->kerning_by_touch = ((ch-'0')&1)?1:0;
		    sub->onlyCloser       = ((ch-'0')&2)?1:0;
		    sub->dontautokern     = ((ch-'0')&4)?1:0;
		    nlgetc(sfd);	/* slurp final bracket */
		} else {
		    ungetc(ch,sfd);
		}
		sub->per_glyph_pst_or_kern = true;
	      break;
	      case gpos_cursive: case gpos_mark2base: case gpos_mark2ligature: case gpos_mark2mark:
		sub->anchor_classes = true;
	      break;
	      default:
	      break;
	    }
	    if ( lastsub==NULL )
		otl->subtables = sub;
	    else
		lastsub->next = sub;
	    lastsub = sub;
	}
	while ( (ch=nlgetc(sfd))==' ' );
	if ( ch=='}' )
	    ch = nlgetc(sfd);
    }
    while ( ch==' ' )
	ch = nlgetc(sfd);
    if ( ch=='[' ) {
	lastfl = NULL;
	for (;;) {
	    while ( (ch=nlgetc(sfd))==' ' );
	    if ( ch==']' )
	break;
	    fl = chunkalloc(sizeof(FeatureScriptLangList));
	    if ( lastfl==NULL )
		otl->features = fl;
	    else
		lastfl->next = fl;
	    lastfl = fl;
	    if ( ch=='<' ) {
		int ft=0,fs=0;
		fscanf(sfd,"%d,%d>", &ft, &fs );
		fl->ismac = true;
		fl->featuretag = (ft<<16) | fs;
	    } else if ( ch=='\'' ) {
		ungetc(ch,sfd);
		fl->featuretag = gettag(sfd);
	    }
	    while ( (ch=nlgetc(sfd))==' ' );
	    if ( ch=='(' ) {
		lastsl = NULL;
		for (;;) {
		    while ( (ch=nlgetc(sfd))==' ' );
		    if ( ch==')' )
		break;
		    sl = chunkalloc(sizeof(struct scriptlanglist));
		    if ( lastsl==NULL )
			fl->scripts = sl;
		    else
			lastsl->next = sl;
		    lastsl = sl;
		    if ( ch=='\'' ) {
			ungetc(ch,sfd);
			sl->script = gettag(sfd);
		    }
		    while ( (ch=nlgetc(sfd))==' ' );
		    if ( ch=='<' ) {
			lcnt = 0;
			for (;;) {
			    while ( (ch=nlgetc(sfd))==' ' );
			    if ( ch=='>' )
			break;
			    if ( ch=='\'' ) {
				ungetc(ch,sfd);
			        if ( lcnt>=lmax )
				    langs = realloc(langs,(lmax+=10)*sizeof(uint32));
				langs[lcnt++] = gettag(sfd);
			    }
			}
			sl->lang_cnt = lcnt;
			if ( lcnt>MAX_LANG )
			    sl->morelangs = malloc((lcnt-MAX_LANG)*sizeof(uint32));
			for ( i=0; i<lcnt; ++i ) {
			    if ( i<MAX_LANG )
				sl->langs[i] = langs[i];
			    else
				sl->morelangs[i-MAX_LANG] = langs[i];
			}
		    }
		}
	    }
	}
    }
    free(langs);
}


static void SFDDumpHintMask(FILE *sfd,HintMask *hintmask) {
    unsigned i, j;

    for ( i=HntMax/8-1; i>0; --i )
	if ( (*hintmask)[i]!=0 )
    break;
    for ( j=0; /* j <= i, but that might never be true, so we test j == i at end of loop */ ; ++j ) {
	if ( ((*hintmask)[j]>>4)<10 )
	    putc('0'+((*hintmask)[j]>>4),sfd);
	else
	    putc('a'-10+((*hintmask)[j]>>4),sfd);
	if ( ((*hintmask)[j]&0xf)<10 )
	    putc('0'+((*hintmask)[j]&0xf),sfd);
	else
	    putc('a'-10+((*hintmask)[j]&0xf),sfd);
        if (j == i) break;
    }
}

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

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

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

int WritePNGInSFD = true;

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

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

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

static const char *end_tt_instrs = "EndTTInstrs";

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

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

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

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

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

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

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

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

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


static void SFDDumpValDevTab(FILE *sfd,ValDevTab *adjust) {
    if ( adjust==NULL )
return;
    fprintf( sfd, " [ddx=" ); SFDDumpDeviceTable(sfd,&adjust->xadjust);
    fprintf( sfd, " ddy=" ); SFDDumpDeviceTable(sfd,&adjust->yadjust);
    fprintf( sfd, " ddh=" ); SFDDumpDeviceTable(sfd,&adjust->xadv);
    fprintf( sfd, " ddv=" ); SFDDumpDeviceTable(sfd,&adjust->yadv);
    putc(']',sfd);
}


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

    unlink(filename);		/* Just in case it's a normal file, it shouldn't be, but just in case... */
    dir = opendir(filename);
    if ( dir==NULL )
return;
    buffer = malloc(strlen(filename)+1+NAME_MAX+1);
    while ( (ent = readdir(dir))!=NULL ) {
	if ( strcmp(ent->d_name,".")==0 || strcmp(ent->d_name,"..")==0 )
    continue;
	pt = strrchr(ent->d_name,EXT_CHAR);
	if ( pt==NULL )
    continue;
	sprintf( buffer,"%s/%s", filename, ent->d_name );
	if ( strcmp(pt,".props")==0 ||
		strcmp(pt,GLYPH_EXT)==0 ||
		strcmp(pt,BITMAP_EXT)==0 )
	    unlink( buffer );
	else if ( strcmp(pt,STRIKE_EXT)==0 ||
		strcmp(pt,SUBFONT_EXT)==0 ||
		strcmp(pt,INSTANCE_EXT)==0 )
	    SFDirClean(buffer);
	/* If there are filenames we don't recognize, leave them. They might contain version control info */
    }
    free(buffer);
    closedir(dir);
}

# define NAME_MAX _POSIX_NAME_MAX

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

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

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

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

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

#define FONT_PROPS	"font.props"

#define STRIKE_PROPS	"strike.props"

#define EXT_CHAR	'.'

#define GLYPH_EXT	".glyph"

#define BITMAP_EXT	".bitmap"

#define STRIKE_EXT	".strike"

#define SUBFONT_EXT	".subfont"

#define INSTANCE_EXT	".instance"


static int ModSF(FILE *asfd,SplineFont *sf) {
    Encoding *newmap;
    int cnt;
    int multilayer=0;
    char tok[200];
    int i,k;
    SplineChar *sc;
    SplineFont *ssf;
    SplineFont temp;
    int layercnt;

    memset(&temp,0,sizeof(temp));
    temp.layers = sf->layers;
    temp.layer_cnt = sf->layer_cnt;
    temp.layers[ly_back].order2 = sf->layers[ly_back].order2;
    temp.layers[ly_fore].order2 = sf->layers[ly_fore].order2;
    temp.ascent = sf->ascent; temp.descent = sf->descent;
    temp.multilayer = sf->multilayer;
    temp.gpos_lookups = sf->gpos_lookups;
    temp.gsub_lookups = sf->gsub_lookups;
    temp.anchor = sf->anchor;
    temp.sfd_version = 2;

    if ( getname(asfd,tok)!=1 || strcmp(tok,"Encoding:")!=0 )
return(false);
    newmap = SFDGetEncoding(asfd,tok);
    if ( getname(asfd,tok)!=1 )
return( false );
    if ( strcmp(tok,"UnicodeInterp:")==0 ) {
	sf->uni_interp = SFDGetUniInterp(asfd,tok,sf);
	if ( getname(asfd,tok)!=1 )
return( false );
    }
    if ( sf->map!=NULL && sf->map->enc!=newmap ) {
	EncMap *map = EncMapFromEncoding(sf,newmap);
	EncMapFree(sf->map);
	sf->map = map;
    }
    temp.map = sf->map;
    if ( strcmp(tok,"LayerCount:")==0 ) {
	getint(asfd,&layercnt);
	if ( layercnt>sf->layer_cnt ) {
	    sf->layers = realloc(sf->layers,layercnt*sizeof(LayerInfo));
	    memset(sf->layers+sf->layer_cnt,0,(layercnt-sf->layer_cnt)*sizeof(LayerInfo));
	}
	sf->layer_cnt = layercnt;
	if ( getname(asfd,tok)!=1 )
return( false );
    }
    while ( strcmp(tok,"Layer:")==0 ) {
	int layer, o2;
	getint(asfd,&layer);
	getint(asfd,&o2);
	if ( layer<sf->layer_cnt ) {
	    sf->layers[layer].order2 = o2;
		if (sf->layers[layer].name)
		    free(sf->layers[layer].name);
	    sf->layers[layer].name = SFDReadUTF7Str(asfd);
	}
	if ( getname(asfd,tok)!=1 )
return( false );
    }
    if ( strcmp(tok,"MultiLayer:")==0 ) {
	getint(asfd,&multilayer);
	if ( getname(asfd,tok)!=1 )
return( false );
    }
    if ( multilayer!=sf->multilayer ) {
	if ( !multilayer )
	    SFSplinesFromLayers(sf,false);
	sf->multilayer = multilayer;
	/* SFLayerChange(sf);*/		/* Shouldn't have any open windows, should not be needed */
    }
    if ( strcmp(tok,"BeginChars:")!=0 )
return(false);
    SFRemoveDependencies(sf);

    getint(asfd,&cnt);
    if ( cnt>sf->glyphcnt ) {
	sf->glyphs = realloc(sf->glyphs,cnt*sizeof(SplineChar *));
	for ( i=sf->glyphcnt; i<cnt; ++i )
	    sf->glyphs[i] = NULL;
	sf->glyphcnt = sf->glyphmax = cnt;
    }
    while ( (sc = SFDGetChar(asfd,&temp,true))!=NULL ) {
	ssf = sf;
	for ( k=0; k<sf->subfontcnt; ++k ) {
	    if ( sc->orig_pos<sf->subfonts[k]->glyphcnt ) {
		ssf = sf->subfonts[k];
		if ( SCWorthOutputting(ssf->glyphs[sc->orig_pos]))
	break;
	    }
	}
	if ( sc->orig_pos<ssf->glyphcnt ) {
	    if ( ssf->glyphs[sc->orig_pos]!=NULL )
		SplineCharFree(ssf->glyphs[sc->orig_pos]);
	    ssf->glyphs[sc->orig_pos] = sc;
	    sc->parent = ssf;
	    sc->changed = true;
	}
    }
    sf->changed = true;
    SFDFixupRefs(sf);
return(true);
}


static void *SFDUnPickle(FILE *sfd, int python_data_has_lists) {
    int ch, quoted;
    static int max = 0;
    static char *buf = NULL;
    char *pt, *end;
    int cnt;

    pt = buf; end = buf+max;
    while ( (ch=nlgetc(sfd))!='"' && ch!='\n' && ch!=EOF );
    if ( ch!='"' )
return( NULL );

    quoted = false;
    while ( ((ch=nlgetc(sfd))!='"' || quoted) && ch!=EOF ) {
	if ( !quoted && ch=='\\' )
	    quoted = true;
	else {
	    if ( pt>=end ) {
		cnt = pt-buf;
		buf = realloc(buf,(max+=200)+1);
		pt = buf+cnt;
		end = buf+max;
	    }
	    *pt++ = ch;
	    quoted = false;
	}
    }
    if ( pt==buf )
return( NULL );
    *pt='\0';
#ifdef _NO_PYTHON
return( copy(buf));
#else
return( PyFF_UnPickleMeToObjects(buf));
#endif
    /* buf is a static buffer, I don't free it, I'll reuse it next time */
}


static void SFDFixupUndoRefs(SplineFont *sf) {
    int i=0;
    Undoes *undo = 0;

    for ( i=0; i<sf->glyphcnt; ++i ) {
	if ( sf->glyphs[i]!=NULL ) {
	    SplineChar *sc = sf->glyphs[i];
	    int layer = 0;
	    for ( layer=0; layer<sc->layer_cnt; ++layer ) {
		if( sc->layers[layer].undoes ) {
		    undo = sc->layers[layer].undoes;
		    SFDFixupUndoRefsUndoList( sf, undo );
		}
		if( sc->layers[layer].redoes ) {
		    undo = sc->layers[layer].redoes;
		    SFDFixupUndoRefsUndoList( sf, undo );
		}
	    }
	}
    }

}


static RefChar *SFDGetRef(FILE *sfd, int was_enc) {
    RefChar *rf;
    int temp=0, ch;

    rf = RefCharCreate();
    getint(sfd,&rf->orig_pos);
    rf->encoded = was_enc;
    if ( getint(sfd,&temp))
	rf->unicode_enc = temp;
    while ( isspace(ch=nlgetc(sfd)));
    if ( ch=='S' ) rf->selected = true;
    getreal(sfd,&rf->transform[0]);
    getreal(sfd,&rf->transform[1]);
    getreal(sfd,&rf->transform[2]);
    getreal(sfd,&rf->transform[3]);
    getreal(sfd,&rf->transform[4]);
    getreal(sfd,&rf->transform[5]);
    while ( (ch=nlgetc(sfd))==' ');
    ungetc(ch,sfd);
    if ( isdigit(ch) ) {
	getint(sfd,&temp);
	rf->use_my_metrics = temp&1;
	rf->round_translation_to_grid = (temp&2)?1:0;
	rf->point_match = (temp&4)?1:0;
	if ( rf->point_match ) {
	    getsint(sfd,(int16 *) &rf->match_pt_base);
	    getsint(sfd,(int16 *) &rf->match_pt_ref);
	    while ( (ch=nlgetc(sfd))==' ');
	    if ( ch=='O' )
		rf->point_match_out_of_date = true;
	    else
		ungetc(ch,sfd);
	}
    }
return( rf );
}


static OTLookup *SFD_ParseNestedLookup(FILE *sfd, SplineFont *sf, int old) {
    uint32 tag;
    int ch, isgpos;
    OTLookup *otl;
    char *name;

    while ( (ch=nlgetc(sfd))==' ' );
    if ( ch=='~' )
return( NULL );
    else if ( old ) {
	if ( ch!='\'' )
return( NULL );

	ungetc(ch,sfd);
	tag = gettag(sfd);
return( (OTLookup *) (intpt) tag );
    } else {
	ungetc(ch,sfd);
	name = SFDReadUTF7Str(sfd);
	if ( name==NULL )
return( NULL );
	for ( isgpos=0; isgpos<2; ++isgpos ) {
	    for ( otl=isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl=otl->next ) {
		if ( strcmp(name,otl->lookup_name )==0 )
	goto break2;
	    }
	}
	break2:
	free(name);
return( otl );
    }
}


SplineChar *SFDReadOneChar(SplineFont *cur_sf,const char *name) {
    FILE *sfd;
    SplineChar *sc=NULL;
    char oldloc[25], tok[2000];
    uint32 pos;
    SplineFont sf;
    LayerInfo layers[2];
    double version;
    int had_layer_cnt=false;
    int chars_seen = false;

    if ( cur_sf->save_to_dir ) {
	snprintf(tok,sizeof(tok),"%s/" FONT_PROPS,cur_sf->filename);
	sfd = fopen(tok,"r");
    } else
	sfd = fopen(cur_sf->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.

    memset(&sf,0,sizeof(sf));
    memset(&layers,0,sizeof(layers));
    sf.layer_cnt = 2;
    sf.layers = layers;
    sf.ascent = 800; sf.descent = 200;
    if ( cur_sf->cidmaster ) cur_sf = cur_sf->cidmaster;
    if ( (version = SFDStartsCorrectly(sfd,tok))>=2 ) {
	sf.sfd_version = version;
	sf.gpos_lookups = cur_sf->gpos_lookups;
	sf.gsub_lookups = cur_sf->gsub_lookups;
	sf.anchor = cur_sf->anchor;
	pos = ftell(sfd);
	while ( getname(sfd,tok)!=-1 ) {
	    if ( strcmp(tok,"StartChar:")==0 ) {
		if ( getname(sfd,tok)==1 && strcmp(tok,name)==0 ) {
		    fseek(sfd,pos,SEEK_SET);
		    sc = SFDGetChar(sfd,&sf,had_layer_cnt);
	break;
		}
	    } else if ( strmatch(tok,"BeginChars:")==0 ) {
		chars_seen = true;
	    } else if ( chars_seen ) {
		/* Don't try to look for things in the file header any more */
		/* The "Layer" keyword has a different meaning in this context */
	    } else if ( strmatch(tok,"Order2:")==0 ) {
		int order2;
		getint(sfd,&order2);
		sf.grid.order2 = order2;
		sf.layers[ly_back].order2 = order2;
		sf.layers[ly_fore].order2 = order2;
	    } else if ( strmatch(tok,"LayerCount:")==0 ) {
		had_layer_cnt = true;
		getint(sfd,&sf.layer_cnt);
		if ( sf.layer_cnt>2 ) {
		    sf.layers = calloc(sf.layer_cnt,sizeof(LayerInfo));
		}
	    } else if ( strmatch(tok,"Layer:")==0 ) {
		int layer, o2;
		getint(sfd,&layer);
		getint(sfd,&o2);
		if ( layer<sf.layer_cnt )
		    sf.layers[layer].order2 = o2;
		free( SFDReadUTF7Str(sfd));
	    } else if ( strmatch(tok,"MultiLayer:")==0 ) {
		int ml;
		getint(sfd,&ml);
		sf.multilayer = ml;
	    } else if ( strmatch(tok,"StrokedFont:")==0 ) {
		int stk;
		getint(sfd,&stk);
		sf.strokedfont = stk;
	    } 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);
	    }
	    pos = ftell(sfd);
	}
    }
    fclose(sfd);
    if ( cur_sf->save_to_dir ) {
	if ( sc!=NULL ) IError("Read a glyph from font.props");
	/* Doesn't work for CID keyed, nor for mm */
	snprintf(tok,sizeof(tok),"%s/%s" GLYPH_EXT,cur_sf->filename,name);
	sfd = fopen(tok,"r");
	if ( sfd!=NULL ) {
	    sc = SFDGetChar(sfd,&sf,had_layer_cnt);
	    fclose(sfd);
	}
    }

    if ( sf.layers!=layers )
	free(sf.layers);
    switch_to_old_locale(&tmplocale, &oldlocale); // Switch to the cached locale.
return( sc );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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

#define FONT_PROPS	"font.props"

#define STRIKE_PROPS	"strike.props"

#define GLYPH_EXT	".glyph"


static void SFDDumpDeviceTable(FILE *sfd,DeviceTable *adjust) {
    int i;

    if ( adjust==NULL )
return;
    fprintf( sfd, "{" );
    if ( adjust->corrections!=NULL ) {
	fprintf( sfd, "%d-%d ", adjust->first_pixel_size, adjust->last_pixel_size );
	for ( i=0; i<=adjust->last_pixel_size-adjust->first_pixel_size; ++i )
	    fprintf( sfd, "%s%d", i==0?"":",", adjust->corrections[i]);
    }
    fprintf( sfd, "}" );
}


static void SFDParseMathValueRecord(FILE *sfd,int16 *value,DeviceTable **devtab) {
    getsint(sfd,value);
    *devtab = SFDReadDeviceTable(sfd,NULL);
}


static void SFDDumpTtfInstrs(FILE *sfd,SplineChar *sc)
{
    SFDDumpTtfInstrsExplicit( sfd, sc->ttf_instrs,sc->ttf_instrs_len );
}


static void SCDefaultInterpolation(SplineChar *sc) {
    SplineSet *cur;
    SplinePoint *sp;
    /* We used not to store the dontinterpolate bit. We used to use the */
    /* presence or absence of instructions as that flag */

    if ( sc->ttf_instrs_len!=0 ) {
	for ( cur=sc->layers[ly_fore].splines; cur!=NULL; cur=cur->next ) {
	    for ( sp=cur->first; ; ) {
		if ( sp->ttfindex!=0xffff && SPInterpolate(sp))
		    sp->dontinterpolate = true;
		if ( sp->next==NULL )
	    break;
		sp=sp->next->to;
		if ( sp==cur->first )
	    break;
	    }
	}
    }
}


SplineFont *SFDirRead(char *filename) {
return( SFD_Read(filename,NULL,true));
}

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

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

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

int WritePNGInSFD = true;

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

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


static void appendnames(char *dest,char *dir,const char *dir_char,char *name,const char *ext ) {
    strcpy(dest,dir);
    dest += strlen(dest);
    strcpy(dest,dir_char);
    dest += strlen(dest);
    /* Some file systems are case-insensitive, so we can't just */
    /* copy the glyph name blindly (else "A" and "a" would map to the same file */
    for (;;) {
	if ( strncmp(name,"uni",3)==0 && ishexdigit(name[3]) && ishexdigit(name[4]) &&
		ishexdigit(name[5]) && ishexdigit(name[6])) {
	    /* but in a name like uni00AD case is irrelevant. Even under unix its */
	    /*  the same as uni00ad -- and it looks ugly */
	    strncpy(dest,name,7);
	    dest += 7; name += 7;
	    while ( ishexdigit(name[0]) && ishexdigit(name[1]) &&
		    ishexdigit(name[2]) && ishexdigit(name[3]) ) {
		strncpy(dest,name,4);
		dest += 4; name += 4;
	    }
	} else if ( name[0]=='u' && ishexdigit(name[1]) && ishexdigit(name[2]) &&
		ishexdigit(name[3]) && ishexdigit(name[4]) &&
		ishexdigit(name[5]) ) {
	    strncpy(dest,name,5);
	    dest += 5; name += 5;
	} else
    break;
	if ( *name!='_' )
    break;
	*dest++ = '_';
	++name;
    }
    while ( *name ) {
	if ( isupper(*name)) {
	    *dest++ = '_';
	    *dest++ = *name;
	} else
	    *dest++ = *name;
	++name;
    }
    strcpy(dest,ext);
}

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

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

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

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

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

#define GLYPH_EXT	".glyph"


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

    while ( isspace(ch = nlgetc(sfd)));
    if ( ch=='#' )
	ch = nlgetc(sfd);
    if ( ch=='-' || ch=='+' ) {
	*pt++ = ch;
	ch = nlgetc(sfd);
    }
    if ( ch=='0' ) {
	ch = nlgetc(sfd);
	if ( ch=='x' || ch=='X' )
	    ch = nlgetc(sfd);
	else {
	    ungetc(ch,sfd);
	    ch = '0';
	}
    }
    while ( isdigit(ch) || (ch>='a' && ch<='f') || (ch>='A' && ch<='F')) {
	if ( pt<end ) *pt++ = ch;
	ch = nlgetc(sfd);
    }
    *pt='\0';
    ungetc(ch,sfd);
    *val = strtoul(tokbuf,NULL,16);
return( pt!=tokbuf?1:ch==EOF?-1: 0 );
}


enum MIME SFDGetImage2MIME(FILE *sfd) {
    char mime[128];

    if ( !getname(sfd, mime) ) {
        IError("Failed to get a MIME type, file corrupt");
        return UNKNOWN;
    }

    if ( !(strmatch(mime, "image/png")==0) ) {
        IError("MIME type received—%s—is not recognized", mime);
        return UNKNOWN;
    }

    return PNG;
}


static int SFD_MIDump(SplineFont *sf,EncMap *map,char *dirname,	int mm_pos) {
    char *instance = malloc(strlen(dirname)+1+10+20);
    char *fontprops;
    FILE *ssfd;
    int err = false;

    /* I'd like to use the font name, but the order of the instances is */
    /*  crucial and I must enforce an ordering on them */
    sprintf( instance,"%s/mm%d" INSTANCE_EXT, dirname, mm_pos );
    GFileMkDir(instance, 0755);
    fontprops = malloc(strlen(instance)+strlen("/" FONT_PROPS)+1);
    strcpy(fontprops,instance); strcat(fontprops,"/" FONT_PROPS);
    ssfd = fopen( fontprops,"w");
    if ( ssfd!=NULL ) {
	err |= SFD_Dump(ssfd,sf,map,NULL,true,instance);
	if ( ferror(ssfd) ) err = true;
	if ( fclose(ssfd)) err = true;
    } else
	err = true;
    free(fontprops);
    free(instance);
return( err );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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

#define FONT_PROPS	"font.props"

#define INSTANCE_EXT	".instance"


static struct ttf_table *SFDGetTtfTable(FILE *sfd, SplineFont *sf,struct ttf_table *lasttab[2]) {
    /* We've read the TtfTable token, it is followed by a tag and a byte count */
    /* and then the instructions in enc85 format */
    int i,len;
    int which;
    struct enc85 dec;
    struct ttf_table *tab = chunkalloc(sizeof(struct ttf_table));

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

    tab->tag = gettag(sfd);

    if ( tab->tag==CHR('f','p','g','m') || tab->tag==CHR('p','r','e','p') ||
	    tab->tag==CHR('c','v','t',' ') || tab->tag==CHR('m','a','x','p'))
	which = 0;
    else
	which = 1;

    getint(sfd,&len);
    tab->data = malloc(len);
    tab->len = len;
    for ( i=0; i<len; ++i )
	tab->data[i] = Dec85(&dec);

    if ( lasttab[which]!=NULL )
	lasttab[which]->next = tab;
    else if ( which==0 )
	sf->ttf_tables = tab;
    else
	sf->ttf_tab_saved = tab;
    lasttab[which] = tab;
return( tab );
}


static SplineFont *SlurpRecovery(FILE *asfd,char *tok,int sizetok) {
    char *pt; int ch;
    SplineFont *sf;

    ch=nlgetc(asfd);
    ungetc(ch,asfd);
    if ( ch=='B' ) {
	if ( getname(asfd,tok)!=1 )
return(NULL);
	if ( strcmp(tok,"Base:")!=0 )
return(NULL);
	while ( isspace(ch=nlgetc(asfd)) && ch!=EOF && ch!='\n' );
	for ( pt=tok; ch!=EOF && ch!='\n'; ch = nlgetc(asfd) )
	    if ( pt<tok+sizetok-2 )
		*pt++ = ch;
	*pt = '\0';
	sf = LoadSplineFont(tok,0);
    } else {
	sf = SplineFontNew();
	sf->onlybitmaps = false;
	strcpy(tok,"<New File>");
    }
    if ( sf==NULL )
return( NULL );

    if ( !ModSF(asfd,sf)) {
	SplineFontFree(sf);
return( NULL );
    }
return( sf );
}


void SFAutoSave(SplineFont *sf,EncMap *map) {
    int i, k, max;
    FILE *asfd;
    SplineFont *ssf;

    if ( no_windowing_ui )		/* No autosaves when just scripting */
return;

    if ( sf->cidmaster!=NULL ) sf=sf->cidmaster;
    asfd = fopen(sf->autosavename,"w");
    if ( asfd==NULL )
return;

    max = sf->glyphcnt;
    for ( i=0; i<sf->subfontcnt; ++i )
	if ( sf->subfonts[i]->glyphcnt>max ) max = sf->subfonts[i]->glyphcnt;

    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->new && sf->origname!=NULL )	/* might be a new file */
	fprintf( asfd, "Base: %s%s\n", sf->origname,
		sf->compression==0?"":compressors[sf->compression-1].ext );
    fprintf( asfd, "Encoding: %s\n", map->enc->enc_name );
    fprintf( asfd, "UnicodeInterp: %s\n", unicode_interp_names[sf->uni_interp]);
    fprintf( asfd, "LayerCount: %d\n", sf->layer_cnt );
    for ( i=0; i<sf->layer_cnt; ++i ) {
	fprintf( asfd, "Layer: %d %d ", i, sf->layers[i].order2 );
	SFDDumpUTF7Str(asfd,sf->layers[i].name);
	putc('\n',asfd);
    }
    if ( sf->multilayer )
	fprintf( asfd, "MultiLayer: %d\n", sf->multilayer );
    fprintf( asfd, "BeginChars: %d\n", max );
    for ( i=0; i<max; ++i ) {
	ssf = sf;
	for ( k=0; k<sf->subfontcnt; ++k ) {
	    if ( i<sf->subfonts[k]->glyphcnt ) {
		ssf = sf->subfonts[k];
		if ( SCWorthOutputting(ssf->glyphs[i]))
	break;
	    }
	}
	if ( ssf->glyphs[i]!=NULL && ssf->glyphs[i]->changed )
	    SFDDumpChar( asfd,ssf->glyphs[i],map,NULL,false,1);
    }
    fprintf( asfd, "EndChars\n" );
    fprintf( asfd, "EndSplineFont\n" );
    fclose(asfd);
    switch_to_old_locale(&tmplocale, &oldlocale); // Switch to the cached locale.
    sf->changed_since_autosave = false;
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

static StemInfo *SFDReadHints(FILE *sfd);

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

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


static char *SFDParseMMSubroutine(FILE *sfd) {
    char buffer[400], *sofar=calloc(1,1);
    const char *endtok = "EndMMSubroutine";
    int len = 0, blen, first=true;

    while ( fgets(buffer,sizeof(buffer),sfd)!=NULL ) {
	if ( strncmp(buffer,endtok,strlen(endtok))==0 )
    break;
	if ( first ) {
	    first = false;
	    if ( strcmp(buffer,"\n")==0 )
    continue;
	}
	blen = strlen(buffer);
	sofar = realloc(sofar,len+blen+1);
	strcpy(sofar+len,buffer);
	len += blen;
    }
    if ( len>0 && sofar[len-1]=='\n' )
	sofar[len-1] = '\0';
return( sofar );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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


static SplineSet *SFDGetSplineSet(FILE *sfd,int order2) {
    SplinePointList *cur=NULL, *head=NULL;
    BasePoint current;
    real stack[100];
    int sp=0;
    SplinePoint *pt = NULL;
    int ch;
	int ch2;
    char tok[100];
    int ttfindex = 0;
    int lastacceptable;
    int flags = 0, tmp;

    current.x = current.y = 0;
    lastacceptable = 0;
    while ( 1 ) {

	while ( getreal(sfd,&stack[sp])==1 )
	    if ( sp<99 )
		++sp;
	while ( isspace(ch=nlgetc(sfd)));
	if ( ch=='E' || ch=='e' || ch==EOF )
    break;
	if ( ch=='S' ) {
	    ungetc(ch,sfd);
	    SFDGetSpiros(sfd,cur);
    continue;
	} else if (( ch=='N' ) &&
	    nlgetc(sfd)=='a' &&	/* a */
	    nlgetc(sfd)=='m' &&	/* m */
	    nlgetc(sfd)=='e' &&	/* e */
	    nlgetc(sfd)=='d' ) /* d */ {
	    ch2 = nlgetc(sfd);		/* : */
		// We are either fetching a splineset name (Named:) or a point name (NamedP:).
		if (ch2=='P') { if ((nlgetc(sfd)==':') && (pt!=NULL)) { if (pt->name!=NULL) {free(pt->name);} pt->name = SFDReadUTF7Str(sfd); } }
		else if (ch2==':') { if (cur != NULL) cur->contour_name = SFDReadUTF7Str(sfd); else { char * freetmp = SFDReadUTF7Str(sfd); free(freetmp); freetmp = NULL; } }
        continue;
	} else if ( ch=='P' && PeekMatch(sfd,"ath") ) {
	    int flags;
	    nlgetc(sfd);		/* a */
	    nlgetc(sfd);		/* t */
	    nlgetc(sfd);		/* h */
	    if (PeekMatch(sfd,"Flags:")) {
	      nlgetc(sfd);		/* F */
	      nlgetc(sfd);		/* l */
	      nlgetc(sfd);		/* a */
	      nlgetc(sfd);		/* g */
	      nlgetc(sfd);		/* s */
	      nlgetc(sfd);		/* : */
	      getint(sfd,&flags);
	      if (cur != NULL) cur->is_clip_path = flags&1;
	    } else if (PeekMatch(sfd,"Start:")) {
	      nlgetc(sfd);		/* S */
	      nlgetc(sfd);		/* t */
	      nlgetc(sfd);		/* a */
	      nlgetc(sfd);		/* r */
	      nlgetc(sfd);		/* t */
	      nlgetc(sfd);		/* : */
	      getint(sfd,&flags);
	      if (cur != NULL) cur->start_offset = flags;
	    }
	}
	pt = NULL;
	if ( ch=='l' || ch=='m' ) {
	    if ( sp>=2 ) {
		current.x = stack[sp-2];
		current.y = stack[sp-1];
		sp -= 2;
		pt = chunkalloc(sizeof(SplinePoint));
		pt->me = current;
		pt->noprevcp = true; pt->nonextcp = true;
		if ( ch=='m' ) {
		    SplinePointList *spl = chunkalloc(sizeof(SplinePointList));
		    spl->first = spl->last = pt;
		    spl->start_offset = 0;
		    if ( cur!=NULL ) {
			if ( !(flags & SFD_PTFLAG_FORCE_OPEN_PATH) && SFDCloseCheck(cur,order2) )
			    --ttfindex;
			cur->next = spl;
		    } else
			head = spl;
		    cur = spl;
		} else {
		    if ( cur!=NULL && cur->first!=NULL && (cur->first!=cur->last || cur->first->next==NULL) ) {
			if ( cur->last->nextcpindex==0xfffe )
			    cur->last->nextcpindex = 0xffff;
			SplineMake(cur->last,pt,order2);
			cur->last->nonextcp = 1;
			pt->noprevcp = 1;
			cur->last = pt;
		    }
		}
	    } else
		sp = 0;
	} else if ( ch=='c' ) {
	    if ( sp>=6 ) {
		current.x = stack[sp-2];
		current.y = stack[sp-1];

		if ( cur!=NULL && cur->first!=NULL && (cur->first!=cur->last || cur->first->next==NULL) ) {
		    cur->last->nextcp.x = stack[sp-6];
		    cur->last->nextcp.y = stack[sp-5];
		    cur->last->nonextcp = false;
		    pt = chunkalloc(sizeof(SplinePoint));
		    pt->prevcp.x = stack[sp-4];
		    pt->prevcp.y = stack[sp-3];
		    pt->me = current;
		    pt->nonextcp = true;
		    if ( cur->last->nextcpindex==0xfffe )
			cur->last->nextcpindex = ttfindex++;
		    else if ( cur->last->nextcpindex!=0xffff )
			ttfindex = cur->last->nextcpindex+1;
		    SplineMake(cur->last,pt,order2);
		    cur->last = pt;
		}

		sp -= 6;
	    } else
		sp = 0;
	}
	if ( pt!=NULL ) {
	    getint(sfd,&flags);

	    pt->pointtype = (flags & SFD_PTFLAG_TYPE_MASK);
	    pt->selected  = (flags & SFD_PTFLAG_IS_SELECTED) > 0;
	    pt->nextcpdef = (flags & SFD_PTFLAG_NEXTCP_IS_DEFAULT) > 0;
	    pt->prevcpdef = (flags & SFD_PTFLAG_PREVCP_IS_DEFAULT) > 0;
	    pt->roundx    = (flags & SFD_PTFLAG_ROUND_IN_X) > 0;
	    pt->roundy    = (flags & SFD_PTFLAG_ROUND_IN_Y) > 0;
	    pt->dontinterpolate = (flags & SFD_PTFLAG_INTERPOLATE_NEVER) > 0;
	    if ( pt->prev!=NULL )
		pt->prev->acceptableextrema = (flags & SFD_PTFLAG_PREV_EXTREMA_MARKED_ACCEPTABLE) > 0;
	    else
		lastacceptable = (flags & SFD_PTFLAG_PREV_EXTREMA_MARKED_ACCEPTABLE) > 0;
	    if ( flags&0x80 )
		pt->ttfindex = 0xffff;
	    else
		pt->ttfindex = ttfindex++;
	    pt->nextcpindex = 0xfffe;
	    ch = nlgetc(sfd);
	    if ( ch=='x' ) {
		pt->hintmask = chunkalloc(sizeof(HintMask));
		SFDGetHintMask(sfd,pt->hintmask);
	    } else if ( ch!=',' )
		ungetc(ch,sfd);
	    else {
		ch = nlgetc(sfd);
		if ( ch==',' )
		    pt->ttfindex = 0xfffe;
		else {
		    ungetc(ch,sfd);
		    getint(sfd,&tmp);
		    pt->ttfindex = tmp;
		    nlgetc(sfd);	/* skip comma */
		    if ( tmp!=-1 )
			ttfindex = tmp+1;
		}
		ch = nlgetc(sfd);
		if ( ch=='\r' || ch=='\n' )
		    ungetc(ch,sfd);
		else {
		    ungetc(ch,sfd);
		    getint(sfd,&tmp);
		    pt->nextcpindex = tmp;
		    if ( tmp!=-1 )
			ttfindex = tmp+1;
		}
	    }
	} else
	    flags = 0;
    }
    if ( cur!=NULL && !(flags & SFD_PTFLAG_FORCE_OPEN_PATH) )
	SFDCloseCheck(cur,order2);
    if ( lastacceptable && cur->last->prev!=NULL )
	cur->last->prev->acceptableextrema = true;
    getname(sfd,tok);
return( head );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

#define SFD_PTFLAG_TYPE_MASK          0x3

#define SFD_PTFLAG_IS_SELECTED        0x4

#define SFD_PTFLAG_NEXTCP_IS_DEFAULT  0x8

#define SFD_PTFLAG_PREVCP_IS_DEFAULT  0x10

#define SFD_PTFLAG_ROUND_IN_X         0x20

#define SFD_PTFLAG_ROUND_IN_Y         0x40

#define SFD_PTFLAG_INTERPOLATE_NEVER  0x100

#define SFD_PTFLAG_PREV_EXTREMA_MARKED_ACCEPTABLE  0x200

#define SFD_PTFLAG_FORCE_OPEN_PATH    0x400

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

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


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

    char* line = 0;
    while((line = getquotedeol( sfd ))) {
        const char** tp = terminators;
        for( ; tp && *tp; ++tp ) {
            if( !strnmatch( line, *tp, strlen( *tp ))) {
                free(line);
                return;
            }
        }
        free(line);
    }
}


Undoes *SFDGetUndo( FILE *sfd, SplineChar *sc,
		    const char* startTag,
		    int current_layer )
{
    Undoes *u = 0;
    char tok[2000];
    int i;
    RefChar *lastr=NULL;
    ImageList *lasti=NULL;
    AnchorPoint *lastap = NULL;
    GuidelineSet *lastgl = NULL;
    SplineChar* tsc = 0;

    if ( getname(sfd,tok)!=1 )
        return( NULL );
    if ( strcmp(tok, startTag) )
        return( NULL );

    u = chunkalloc(sizeof(Undoes));
    u->undotype = ut_state;
    u->layer = UNDO_LAYER_UNKNOWN;

    while ( 1 )
    {
        if ( getname(sfd,tok)!=1 ) {
            chunkfree(u,sizeof(Undoes));
            return( NULL );
        }

        if ( !strmatch(tok,"EndUndoOperation")
            || !strmatch(tok,"EndRedoOperation"))
        {
            if( u->undotype == ut_hints ) {
                if( tsc ) {
                    u->u.state.hints = UHintCopy(tsc,1);
                    SplineCharFree( tsc );
                }
            }

            return u;
        }
	if ( !strmatch(tok,"Index:")) {
            getint(sfd,&i);
        }
	if ( !strmatch(tok,"Type:")) {
            getint(sfd,&i);
            u->undotype = i;
            if( u->undotype == ut_hints ) {
                tsc = SplineCharCopy( sc, 0, 0 );
                tsc->hstem = 0;
                tsc->vstem = 0;
                tsc->dstem = 0;
            }
        }
        if ( !strmatch(tok,"WasModified:")) {
            getint(sfd,&i);
            u->was_modified = i;
        }
        if ( !strmatch(tok,"WasOrder2:")) {
            getint(sfd,&i);
            u->was_order2 = i;
        }
        if ( !strmatch(tok,"Layer:")) {
            getint(sfd,&i);
            u->layer = i;
        }

        switch( u->undotype )
        {
	case ut_tstate:
	case ut_state:
	    if ( !strmatch(tok,"Width:"))          { getint(sfd,&i); u->u.state.width = i; }
	    if ( !strmatch(tok,"VWidth:"))         { getint(sfd,&i); u->u.state.vwidth = i; }
	    if ( !strmatch(tok,"LBearingChange:")) { getint(sfd,&i); u->u.state.lbearingchange = i; }
	    if ( !strmatch(tok,"UnicodeEnc:"))     { getint(sfd,&i); u->u.state.unicodeenc = i; }
	    if ( !strmatch(tok,"Charname:"))       { u->u.state.charname = getquotedeol(sfd); }
	    if ( !strmatch(tok,"Comment:"))        { u->u.state.comment  = getquotedeol(sfd); }

	    if( !strmatch(tok,"Refer:"))
	    {
		RefChar *ref = SFDGetRef(sfd,strmatch(tok,"Ref:")==0);
		int i=0;
		for( i=0; i< ref->layer_cnt; i++ ) {
		    ref->layers[i].splines = 0;
		}
		if ( !u->u.state.refs )
		    u->u.state.refs = ref;
		else
		    lastr->next = ref;
		lastr = ref;
	    }

	    if( !strmatch(tok,"Image:"))
	    {
		ImageList *img = SFDGetImage(sfd);
		if (img != NULL) {
		if ( !u->u.state.images )
		    u->u.state.images = img;
		else
		    lasti->next = img;
		lasti = img;
		}
	    }

	    if( !strmatch(tok,"Image2:"))
	    {
#ifndef _NO_LIBPNG
		enum MIME mime = SFDGetImage2MIME(sfd);
		if (mime == PNG) {
		    ImageList *img = SFDGetImagePNG(sfd);
		    if (img != NULL) {
			if ( !u->u.state.images )
			    u->u.state.images = img;
			else
			    lasti->next = img;
			lasti = img;
		    }
		} else
#endif
	    {
		LogError(_("Image2 skipped as it uses an unsupported image type"));
		const char* im2_terminator[] = { "EndImage2", 0 };
		SFDConsumeUntil(sfd, im2_terminator);
	    }
	    }

	    if( !strmatch(tok,"Comment:")) {
		u->u.state.comment  = getquotedeol(sfd);
	    }
	    if( !strmatch(tok,"InstructionsLength:")) {
		getint(sfd,&i); u->u.state.instrs_len = i;
	    }
	    if( !strmatch(tok,"AnchorPoint:") ) {
		lastap = SFDReadAnchorPoints( sfd, sc, &(u->u.state.anchor), lastap );
	    }
	    if ( !strmatch(tok,"SplineSet")) {
		u->u.state.splines = SFDGetSplineSet(sfd,sc->layers[current_layer].order2);
	    }
	    break;
	case ut_hints:
	{
	    if ( !strmatch(tok,"HStem:") ) {
		tsc->hstem = SFDReadHints(sfd);
		tsc->hconflicts = StemListAnyConflicts(tsc->hstem);
	    }
	    else if ( !strmatch(tok,"VStem:") ) {
		tsc->vstem = SFDReadHints(sfd);
		tsc->vconflicts = StemListAnyConflicts(tsc->vstem);
	    }
	    else if( !strmatch(tok,"DStem2:"))
		tsc->dstem = SFDReadDHints( sc->parent,sfd,false );
	    else if( !strmatch(tok,"TtInstrs:")) {
		SFDGetTtInstrs(sfd,tsc);
		u->u.state.instrs = tsc->ttf_instrs;
		u->u.state.instrs_len = tsc->ttf_instrs_len;
		tsc->ttf_instrs = 0;
		tsc->ttf_instrs_len = 0;
	    }
	    break;
	}

	case ut_width:
	case ut_vwidth:
	    if( !strmatch(tok,"Width:")) {
		getint(sfd,&i); u->u.width = i;
	    }
	    break;
	default:
	break;
        }
    }

    return u;
}


void SFDGetKerns( FILE *sfd, SplineChar *sc, char* ttok ) {
    struct splinefont * sf = sc->parent;
    char tok[2001], ch;
    uint32 script = 0;
    SplineFont *sli_sf = sf->cidmaster ? sf->cidmaster : sf;

    strncpy(tok,ttok,sizeof(tok)-1);
    tok[2000]=0;

    if( strmatch(tok,"Kerns2:")==0 ||
	strmatch(tok,"VKerns2:")==0 ) {
	    KernPair *kp, *last=NULL;
	    int isv = *tok=='V';
	    int off, index;
	    struct lookup_subtable *sub;
	    int kernCount = 0;
	    if ( sf->sfd_version<2 )
		LogError(_("Found an new style kerning pair inside a version 1 (or lower) sfd file.\n") );
	    while ( fscanf(sfd,"%d %d", &index, &off )==2 ) {
		sub = SFFindLookupSubtableAndFreeName(sf,SFDReadUTF7Str(sfd));
		if ( sub==NULL ) {
		    LogError(_("KernPair with no subtable name.\n"));
	    	    break;
		}
		kernCount++;
		kp = chunkalloc(sizeof(KernPair1));
		kp->sc = (SplineChar *) (intpt) index;
		kp->kcid = true;
		kp->off = off;
		kp->subtable = sub;
		kp->next = NULL;
		while ( (ch=nlgetc(sfd))==' ' );
		ungetc(ch,sfd);
		if ( ch=='{' ) {
		    kp->adjust = SFDReadDeviceTable(sfd, NULL);
		}
		if ( last != NULL )
		    last->next = kp;
		else if ( isv )
		    sc->vkerns = kp;
		else
		    sc->kerns = kp;
		last = kp;
	    }
	    if( !kernCount ) {
//		printf("SFDGetKerns() have a BLANK KERN\n");
		sc->kerns = 0;
	    }
    } else if ( strmatch(tok,"Kerns:")==0 ||
		strmatch(tok,"KernsSLI:")==0 ||
		strmatch(tok,"KernsSLIF:")==0 ||
		strmatch(tok,"VKernsSLIF:")==0 ||
		strmatch(tok,"KernsSLIFO:")==0 ||
		strmatch(tok,"VKernsSLIFO:")==0 ) {
	    KernPair1 *kp, *last=NULL;
	    int index, off, sli, flags=0;
	    int hassli = (strmatch(tok,"KernsSLI:")==0);
	    int isv = *tok=='V';
	    int has_orig = strstr(tok,"SLIFO:")!=NULL;
	    if ( sf->sfd_version>=2 ) {
		IError( "Found an old style kerning pair inside a version 2 (or higher) sfd file." );
exit(1);
	    }
	    if ( strmatch(tok,"KernsSLIF:")==0 || strmatch(tok,"KernsSLIFO:")==0 ||
		    strmatch(tok,"VKernsSLIF:")==0 || strmatch(tok,"VKernsSLIFO:")==0 )
		hassli=2;
	    while ( (hassli==1 && fscanf(sfd,"%d %d %d", &index, &off, &sli )==3) ||
		    (hassli==2 && fscanf(sfd,"%d %d %d %d", &index, &off, &sli, &flags )==4) ||
		    (hassli==0 && fscanf(sfd,"%d %d", &index, &off )==2) ) {
		if ( !hassli )
		    sli = SFFindBiggestScriptLangIndex(sli_sf,
			    script!=0?script:SCScriptFromUnicode(sc),DEFAULT_LANG);
		if ( sli>=((SplineFont1 *) sli_sf)->sli_cnt && sli!=SLI_NESTED) {
		    static int complained=false;
		    if ( !complained )
			IError("'%s' in %s has a script index out of bounds: %d",
				isv ? "vkrn" : "kern",
				sc->name, sli );
		    sli = SFFindBiggestScriptLangIndex(sli_sf,
			    SCScriptFromUnicode(sc),DEFAULT_LANG);
		    complained = true;
		}
		kp = chunkalloc(sizeof(KernPair1));
		kp->kp.sc = (SplineChar *) (intpt) index;
		kp->kp.kcid = has_orig;
		kp->kp.off = off;
		kp->sli = sli;
		kp->flags = flags;
		kp->kp.next = NULL;
		while ( (ch=nlgetc(sfd))==' ' );
		ungetc(ch,sfd);
		if ( ch=='{' ) {
		    kp->kp.adjust = SFDReadDeviceTable(sfd, NULL);
		}
		if ( last != NULL )
		    last->kp.next = (KernPair *) kp;
		else if ( isv )
		    sc->vkerns = (KernPair *) kp;
		else
		    sc->kerns = (KernPair *) kp;
		last = kp;
	    }
    } else {
	return;
    }

    // we matched something, grab the next top level token to ttok
    getname( sfd, ttok );
}


static int gethexints(FILE *sfd, uint32 *val, int cnt) {
    int i, ch;

    for ( i=0; i<cnt; ++i ) {
	if ( i!=0 ) {
	    ch = nlgetc(sfd);
	    if ( ch!='.' ) ungetc(ch,sfd);
	}
	if ( !gethex(sfd,&val[i]))
return( false );
    }
return( true );
}

int UndoRedoLimitToSave = 0;

int UndoRedoLimitToLoad = 0;

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

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

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

int prefRevisionsToRetain = 32;

int WritePNGInSFD = true;

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

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

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

static const char *end_tt_instrs = "EndTTInstrs";

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

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

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

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

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

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

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