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,