FontForge and Glyphs

UFO is not the only human-readable font-format. FontForge stores its fonts in the SFD format. In RoboFab there is a script by EvB to work with this format (RoboFab/robofab/objects/objectsFF.py).
Any chance to im-/export (read/write) SFD directly into Glyphs?
And perhaps im-/export (read/write) Glyphs-files directly into FontForge?

The chance for SFD support in Glyphs is very high as it should be easy to write a plugin. If someone is interested in implementing it, I could assist.

G

Any further developement on this?
Maybe someone from the FontForge-team would be interested to do this?

form the FontForge sources: ufo.c

Matbe useful create an im-/export-plugin to read/write *.sfd fontfiles from Glyphs.

/* Copyright © 2003-2009 by George Williams /
/

  • Redistribution and use in source and binary forms, with or without

  • modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this

  • list of conditions and the following disclaimer.

  • Redistributions in binary form must reproduce the above copyright notice,

  • this list of conditions and the following disclaimer in the documentation

  • and/or other materials provided with the distribution.

  • The name of the author may not be used to endorse or promote products

  • derived from this software without specific prior written permission.

  • THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS’’ AND ANY EXPRESS OR IMPLIED

  • WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF

  • MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO

  • EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,

  • SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,

  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;

  • OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,

  • WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR

  • OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF

  • ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    */

#ifdef HAVE_CONFIG_H

include “config.h”

#endif

#ifndef _NO_PYTHON

include “Python.h”

include “structmember.h”

#else

include <utype.h>

#endif

#include “fontforgevw.h”
#include <unistd.h>
#include <math.h>
#include <time.h>
#include <locale.h>
#include <chardata.h>
#include <gfile.h>
#include <ustring.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#ifndef _NO_PYTHON

include “ffpython.h”

#endif

/* The UFO (Unified Font Object) format, http://unifiedfontobject.org/ /
/
Obsolete: http://just.letterror.com/ltrwiki/UnifiedFontObject /
/
is a directory containing a bunch of (mac style) property lists and another*/
/* directory containing glif files (and contents.plist). */

/* Property lists contain one element which contains elements /
/
each followed by an , , , or element, /
/
or another */

/* UFO format 2.0 includes an adobe feature file “feature.fea” and slightly /
/
different/more tags in fontinfo.plist */

static char *buildname(char *basedir,char *sub) {
char *fname = galloc(strlen(basedir)+strlen(sub)+2);

strcpy(fname, basedir);
if ( fname[strlen(fname)-1]!='/' )
strcat(fname,"/");
strcat(fname,sub);

return( fname );
}

/* ************************************************************************** /
/
************************* Python lib Output ************************* /
/
************************************************************************** */
#ifndef _NO_PYTHON
static int PyObjDumpable(PyObject *value);
static void DumpPyObject( FILE *file, PyObject *value );
#endif

static void DumpPythonLib(FILE *file,void *python_persistent,SplineChar *sc) {
StemInfo *h;
int has_hints = (sc!=NULL && (sc->hstem!=NULL || sc->vstem!=NULL ));

#ifdef _NO_PYTHON
if ( has_hints ) {
/* Not officially part of the UFO/glif spec, but used by robofab */
fprintf( file, " \n" );
fprintf( file, " \n" );
#else
PyObject *dict = python_persistent, *items, *key, *value;
int i, len;
char *str;

if ( has_hints || (dict!=NULL && PyMapping_Check(dict)) ) {
if ( sc!=NULL ) {
    fprintf( file, "  <lib>\n" );
    fprintf( file, "    <dict>\n" );
}
if ( has_hints ) {

#endif
fprintf( file, " com.fontlab.hintData\n" );
fprintf( file, " \n" );
if ( sc->hstem!=NULL ) {
fprintf( file, “\thhints\n” );
fprintf( file, “\t\n” );
for ( h = sc->hstem; h!=NULL; h=h->next ) {
fprintf( file, “\t \n” );
fprintf( file, “\t position” );
fprintf( file, “\t %d\n”, (int) rint(h->start));
fprintf( file, “\t width” );
fprintf( file, “\t %d\n”, (int) rint(h->width));
fprintf( file, “\t \n” );
}
fprintf( file, “\t\n” );
}
if ( sc->vstem!=NULL ) {
fprintf( file, “\tvhints\n” );
fprintf( file, “\t\n” );
for ( h = sc->vstem; h!=NULL; h=h->next ) {
fprintf( file, “\t \n” );
fprintf( file, “\t position\n” );
fprintf( file, “\t %d\n”, (int) rint(h->start));
fprintf( file, “\t width\n” );
fprintf( file, “\t %d\n”, (int) rint(h->width));
fprintf( file, “\t \n” );
}
fprintf( file, “\t\n” );
}
fprintf( file, " \n" );
#ifndef _NO_PYTHON
}
/* Ok, look at the persistent data and output it (all except for a /
/
hint entry – we’ve already handled that with the real hints, /
/
no point in retaining out of date hints too */
if ( dict != NULL ) {
items = PyMapping_Items(dict);
len = PySequence_Size(items);
for ( i=0; i<len; ++i ) {
PyObject item = PySequence_GetItem(items,i);
key = PyTuple_GetItem(item,0);
if ( !PyBytes_Check(key)) /
Keys need not be strings /
continue;
str = PyBytes_AsString(key);
if ( strcmp(str,“com.fontlab.hintData”)==0 && sc!=NULL ) /
Already done */
continue;
value = PyTuple_GetItem(item,1);
if ( !PyObjDumpable(value))
continue;
fprintf( file, " %s\n", str );
DumpPyObject( file, value );
}
}
#endif
if ( sc!=NULL ) {
fprintf( file, " \n" );
fprintf( file, " \n" );
}
}
}

#ifndef _NO_PYTHON
static int PyObjDumpable(PyObject value) {
if ( PyInt_Check(value))
return( true );
if ( PyFloat_Check(value))
return( true );
if ( PySequence_Check(value)) /
Catches strings and tuples */
return( true );
if ( PyMapping_Check(value))
return( true );
if ( PyBool_Check(value))
return( true );
if ( value == Py_None )
return( true );

return( false );
}

static void DumpPyObject( FILE *file, PyObject value ) {
if ( PyMapping_Check(value))
DumpPythonLib(file,value,NULL);
else if ( PyBytes_Check(value)) { /
Must precede the sequence check */
char *str = PyBytes_AsString(value);
fprintf( file, " %s\n", str );
} else if ( value==Py_True )
fprintf( file, " \n" );
else if ( value==Py_False )
fprintf( file, " \n" );
else if ( value==Py_None )
fprintf( file, " \n" );
else if (PyInt_Check(value))
fprintf( file, " %ld\n", PyInt_AsLong(value) );
else if (PyInt_Check(value))
fprintf( file, " %g\n", PyFloat_AsDouble(value) );
else if (PySequence_Check(value)) {
int i, len = PySequence_Size(value);

fprintf( file, "      <array>\n" );
for ( i=0; i<len; ++i ) {
    PyObject *obj = PySequence_GetItem(value,i);
    if ( PyObjDumpable(obj)) {
	fprintf( file, "  ");
	DumpPyObject(file,obj);
    }
}
fprintf( file, "      </array>\n" );
}

}
#endif

/* ************************************************************************** /
/
**************************** GLIF Output **************************** /
/
************************************************************************** */
static int _GlifDump(FILE *glif,SplineChar *sc,int layer) {
struct altuni *altuni;
int isquad = sc->layers[layer].order2;
SplineSet *spl;
SplinePoint *sp;
RefChar *ref;
int err;

if ( glif==NULL )

return( false );

fprintf( glif, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
/* No DTD for these guys??? */
fprintf( glif, "<glyph name=\"%s\" format=\"1\">\n", sc->name );
if ( sc->parent->hasvmetrics )
fprintf( glif, "  <advance width=\"%d\" height=\"%d\"/>\n", sc->width, sc->vwidth );
else
fprintf( glif, "  <advance width=\"%d\"/>\n", sc->width );
if ( sc->unicodeenc!=-1 )
fprintf( glif, "  <unicode hex=\"%04x\"/>\n", sc->unicodeenc );
for ( altuni = sc->altuni; altuni!=NULL; altuni = altuni->next )
if ( altuni->vs==-1 && altuni->fid==0 )
    fprintf( glif, "  <unicode hex=\"%04x\"/>\n", altuni->unienc );

if ( sc->layers[layer].refs!=NULL || sc->layers[layer].splines!=NULL ) {
fprintf( glif, "  <outline>\n" );
for ( ref = sc->layers[layer].refs; ref!=NULL; ref=ref->next ) if ( SCWorthOutputting(ref->sc)) {
    fprintf( glif, "    <component base=\"%s\"", ref->sc->name );
    if ( ref->transform[0]!=1 )
	fprintf( glif, " xScale=\"%g\"", (double) ref->transform[0] );
    if ( ref->transform[3]!=1 )
	fprintf( glif, " yScale=\"%g\"", (double) ref->transform[3] );
    if ( ref->transform[1]!=0 )
	fprintf( glif, " xyScale=\"%g\"", (double) ref->transform[1] );
    if ( ref->transform[2]!=0 )
	fprintf( glif, " yxScale=\"%g\"", (double) ref->transform[2] );
    if ( ref->transform[4]!=0 )
	fprintf( glif, " xOffset=\"%g\"", (double) ref->transform[4] );
    if ( ref->transform[5]!=0 )
	fprintf( glif, " yOffset=\"%g\"", (double) ref->transform[5] );
    fprintf( glif, "/>\n" );
}
for ( spl=sc->layers[layer].splines; spl!=NULL; spl=spl->next ) {
    fprintf( glif, "    <contour>\n" );
    for ( sp=spl->first; sp!=NULL; ) {
	/* Undocumented fact: If a contour contains a series of off-curve points with no on-curve then treat as quadratic even if no qcurve */
	if ( !isquad || /*sp==spl->first ||*/ !SPInterpolate(sp) )
	    fprintf( glif,