Major differences between FontForge's and Adobe's interpretation of feature files

Adobe specifies the syntax for their feature files. Sadly this syntax is not adequate for specifying all of OpenType.

  1. Adobe does not support anchors, nor any of the lookups that use anchors. Adobe does provide a syntax for these, but notes that their tools do not (yet) use that syntax, and that the syntax may change in the future.
    FontForge does support the documented syntax (and I hope it doesn't change in the future).
  2. Adobe does not support device tables. Again Adobe does provide a syntax for them (with the caveat that that syntax may change).
    FontForge does support the documented syntax (assuming FF has been configured with device table support -- if not then FF will simply parse and ignore device tables)
  3. Adobe does not support ligature carets. Again Adobe does provide a syntax (with the standard caveat).
    FontForge does support the documented syntax.
  4. Adobe does not support several substitutions within a single contextual lookup rule. But they do provide a syntax.
    FontForge does support the documented syntax.
  5. Adobe does not provide a syntax for contextual lookups where leading or final glyphs in the match sequence (not backtrack, not lookahead) are not substituted or positioned. I think in complicated lookups this may be problematic...
  6. Adobe does not provide a syntax for contextual multiple substitution (this would be ambiguous with the documented but unsupported syntax for several substitutions within one contextual rule).
    I have made one extension to the syntax to allow for this.
  7. Adobe does not support contextual mark positioning. They do provide a syntax for it, but the syntax does not seem adequate to me (as I don't see how the mark anchors are specified).
    FontForge uses the extension mentioned above for this.
  8. Adobe does not support the "required" keyword for features
    FontForge doesn't either but will parse and ignore it.
  9. Adobe does not support GDEF mark classes. Nor do they provide a syntax for them.
    FontForge does not support this concept either.
  10. Adobe does not support reverse contextual substitution lookups. Nor do they provide a syntax.
    FontForge does not support this either.
  11. Adobe allows you to specify that a lookup should be made into an extension lookup.
    FontForge will parse and ignore this. It will decide whether a lookup should become an extension lookup when it writes out the tables. It will put lookups in extension lookups if they would require 4 byte offsets.
  12. FontForge will parse and ignore "anonymous" tables. I can't figure out from the spec how they should be dealt with.
  13. FontForge does not support all the table fields that can be set in a feature file. For instance FontForge fills in the CodePageRange field of the OS/2 table depending on what characters are in the font, not on what users say is in the font -- so FontForge will parse and ignore any attempt to set this field.
  14. FontForge will not create 'aalt' lookups the way the FDK does. Instead FF will parse and ignore the 'feature' statements within an 'aalt' feature. FontForge has its own commands for creating 'aalt' (Font Info->Lookups->(popup menu)->Add 'aalt' features)

Adobe tells me there is a new version of the FDK coming out later this summer (2007) which will address these issues.

FontForge's extension

When FontForge finds a contextual lookup for which there is no syntax, it will describe the controlled lookup in its own lookup block, and provide a pointer to that lookup in the contextual.

So, suppose you wanted a contextual lookup to decompose the "ff" ligature. This might be desireable when outputting hex numbers. FontForge would produce:

lookup ff_decomp_nested {
  subs ff by f f
} ff_decomp_nested;

@hexdigits=[zero one two three four five six seven eight nine a-f]
subs @hexdigits ff' by <lookup ff_decomp_nested>
subs ff' @hexdigits by <lookup ff_decomp_nested>

Parsing a feature file

Use File->Merge Feature Info (formerly File->Merge Kern Info)

Outputting a feature file

Use Element->Font Info->Lookups, then right click on any lookup (to produce a popup menu) and select Save Feature File

You can also generate a feature file containing a single lookup by selecting that lookup, producing the popup menu and selecting Save Lookup

Example Feature File

# GSUB

lookup idecomposition {
  lookupflag 0;
    sub i' [acutecomb gravecomb ] by dotlessi ;
    sub i' dotbelowcomb [acutecomb gravecomb ] by dotlessi ;
} idecomposition;

lookup Numericsuperscripts {
  lookupflag 0;
    sub zero by zero.superior ;
    sub one by onesuperior ;
    ... (substitutions omitted)
    sub nine by nine.superior ;
} Numericsuperscripts;

lookup smcpLowercasetoSmallCapitalsinL {
  lookupflag 0;
    sub a by a.sc ;
    sub b by b.sc ;
    ... (substitutions omitted)
    sub z by z.sc ;
} smcpLowercasetoSmallCapitalsinL;

lookup ligaStandardLigaturesinLatinloo {
  lookupflag 0;
    sub f i  by fi;
    sub f l  by fl;
    sub f f  by ff;
    sub f f i  by ffi;
    sub f f l  by ffl;
} ligaStandardLigaturesinLatinloo;

lookup dligDiscretionaryLigaturesinLat {
  lookupflag 0;
    sub c h  by c_h;
    sub c t  by c_t;
    sub f b  by f_b;
    sub f r  by f_r;
    sub f t  by f_t;
    sub longs h  by longs_h;
    sub s h  by s_h;
    sub longs i  by longs_i;
    sub longs l  by longs_l;
    sub longs longs  by longs_longs;
    sub longs longs i  by longs_longs_i;
    sub longs longs l  by longs_longs_l;
    sub longs t  by longs_t;
    sub s t  by s_t;
    sub I J  by IJ;
    sub i j  by ij;
} dligDiscretionaryLigaturesinLat;

lookup fracDiagonalFractionslookup3 {
  lookupflag 0;
    sub one slash two  by onehalf;
    sub one fraction two  by onehalf;
    sub one slash four  by onequarter;
    sub one fraction four  by onequarter;
    sub three slash four  by threequarters;
    sub three fraction four  by threequarters;
} fracDiagonalFractionslookup3;

feature \mark {
  script DFLT;
     language dflt ;
      lookup idecomposition;

  script latn;
     language dflt ;
      lookup idecomposition;
} \mark;

feature subs {
  script DFLT;
     language dflt ;
      lookup Numericsuperscripts;

  script cyrl;
     language dflt ;
      lookup Numericsuperscripts;

  script grek;
     language dflt ;
      lookup Numericsuperscripts;

  script latn;
     language dflt ;
      lookup Numericsuperscripts;
} subs;

feature sups {
  script DFLT;
     language dflt ;
      lookup Numericsuperscripts;

  script cyrl;
     language dflt ;
      lookup Numericsuperscripts;

  script grek;
     language dflt ;
      lookup Numericsuperscripts;

  script latn;
     language dflt ;
      lookup Numericsuperscripts;
} sups;

feature smcp {
  script latn;
     language dflt ;
      lookup smcpLowercasetoSmallCapitalsinL;
} smcp;

feature liga {
  script latn;
     language dflt ;
      lookup ligaStandardLigaturesinLatinloo;
} liga;

feature dlig {
  script latn;
     language dflt ;
      lookup dligDiscretionaryLigaturesinLat;
} dlig;

feature frac {
  script DFLT;
     language dflt ;
      lookup fracDiagonalFractionslookup3;
} frac;

# GPOS 

lookup LatinMarks {
  lookupflag 0;
    mark dotbelowcomb <anchor -164 -45>;
    pos a <anchor 207 -54> mark [dotbelowcomb ];
    pos e <anchor 222 -54> mark [dotbelowcomb ];
    pos i <anchor 60 -54> mark [dotbelowcomb ];
    pos o <anchor 165 -54> mark [dotbelowcomb ];
    pos u <anchor 213 -54> mark [dotbelowcomb ];
    pos dotlessi <anchor 60 -54> mark [dotbelowcomb ];
    mark acutecomb <anchor -163 598>;
    mark gravecomb <anchor -108 598>;
    pos a <anchor 180 558> mark [acutecomb gravecomb ];
    pos e <anchor 180 558> mark [acutecomb gravecomb ];
    pos o <anchor 180 558> mark [acutecomb gravecomb ];
    pos u <anchor 164 558> mark [acutecomb gravecomb ];
    pos dotlessi <anchor 68 558> mark [acutecomb gravecomb ];
} LatinMarks;

lookup Subscriptadjustmenttosuperscrip {
  lookupflag 0;
    pos onesuperior <0 -850 0 0>;
    pos threesuperior <0 -850 0 0>;
    ... (positionings omitted)
    pos six.superior <0 -850 0 0>;
} Subscriptadjustmenttosuperscrip;

lookup kernHorizontalKerninginLatinloo {
  lookupflag 0;
    pos T u -109;
    pos T o -103;
    pos T e -121;
    pos T a -97;
    pos V e -36;
    pos W u -30;
    pos W o -42;
    pos W e -48;
    pos X u -97;
    pos Y u -48;
    pos Y o -73;
    pos Y e -60;
    pos Y a -54;
} kernHorizontalKerninginLatinloo;

feature \mark {
  script DFLT;
     language dflt ;
      lookup LatinMarks;

  script latn;
     language dflt ;
      lookup LatinMarks;
} \mark;

feature subs {
  script DFLT;
     language dflt ;
      lookup Subscriptadjustmenttosuperscrip;

  script cyrl;
     language dflt ;
      lookup Subscriptadjustmenttosuperscrip;

  script grek;
     language dflt ;
      lookup Subscriptadjustmenttosuperscrip;

  script latn;
     language dflt ;
      lookup Subscriptadjustmenttosuperscrip;
} subs;

feature kern {
  script latn;
     language dflt ;
      lookup kernHorizontalKerninginLatinloo;
} kern;