3

I'm working on coding a custom font in FontForge for some positional alternates of letters. I am not working with feature files for alternates, but using the FontForge interface for building lookups (I'm not totally opposed to a file solution, but based off what I've seen via an exported lookup to a .fea file from FontForge, the file syntax does not seem to quite match what that link talks about, so even via file, I'm not sure how to implement this).

In that link above, it notes that when working with that type of file, one can do an "ignore" for a pattern. Assuming here that one has a medial form of a letter and an initial form, the any medial form that is preceded by another letter does not get switched to the initial form:

ignore sub @AllLetters @Medial';
sub @Medial' by @Initial;

In otherwords, there is a way to set a pattern (or patterns) to ignore doing a lookup, before the actual lookup call, to limit when the lookup is done.

However, I cannot find any documentation stating how to code that same type of ignore statement when using the lookup dialog boxes for building lookup patterns. So in my lookup subtable for contextual chaining substitutions, I have this so far that is built (with dummy names here):

| className @<mySubstitutionLookup> |

But if I put any ignore in, then it states that "ignore is not a class name for the matching classes" (which I understand why it is saying that, since I have not defined an "ignore" class, but neither should I need to if it were a keyword).

So apparently, in the lookup dialog...

enter image description here

... ignore statements need to be coded differently, but again, I cannot find documentation of how (or even if, though I would think they could) those dialog boxes can code that.

So how does one get an ignore statement to code into a contextual chaining substitution using the program's dialog boxes? And if a FontForge .fea file can do it, how exactly is that done with the variation of syntax?

2 Answers 2

2

FontForge is crazy confusing, so here's the general "how to do this" answer in case it's helpful to anyone. First, understand that each rule in your Chaining Substitution list has this form:

backClass | matchClass @<Lookup Table> | forwardClass

The way a rule works is this: it checks each rule in sequence, looking for a match. If it finds a match, it stops looking, and then translates the characters matched through the selected Lookup Table. The backClass and forwardClass are optional, but the matchClass represents the characters that will change, using the specified Lookup Table to determine what to replace them with.

Generally, the way you do what you are describing is that you need to have TWO lookup (sub) tables. In all the examples below, pretend that a is the regular character and A is the fancy initial version.

First, in your list of lookup tables, you will have the "Composition" lookup:

a -> A
b -> B
c -> C

Then you also define a lookup table, your "Decomposition" lookup:

A -> a
B -> b
C -> c

(In your case, both are just substitute tables; if you were doing ligatures it might be a multiple substitution character.)

Last, comes your Contextual Chaining table. You would define your rule like so:

Rule: letters | initialLetters @<Decomposition Lookup> |

      ^ back    ^ matching     ^ lookup                  ^ forward

Class: letters        = a b c d e f ....
Class: initialLetters = A B C D E F G

This rule tells the font "if you see a glyph in {letters}, followed by a glyph in {initialLetters}, transform the glyph in {initialLetters} - aka, the matched glyph - using the lookup table Decomposition Lookup". So for example, aA becomes aa, because the Decomposition Lookup specifies A -> a.

Finally, very importantly, you need to order your lookup tables in your font like so:

Decomposition Lookup (A -> a)
Composition Lookup (a -> A)
Contextual Chaining

Why? As the glyph stream is processed, first it will reset all potential alternate glyphs (by applying decomposition)*. Next, it unilaterally applies all alternate glyphs (by applying composition). Last, it selectively "undoes" the alternate glyphs (using the contextual chaining lookup), by re-applying the Decomposition Lookup only where your matching rules apply.

* In reality, the decomposition lookup won't do anything because usually your "alternate" or "ligature" forms can't actually exist in the incoming glyph stream. It's still important it goes before the composition lookup, otherwise, it will immediately undo all of the alternate glyphs you specified.

Hope this helps...

UPDATE: I forgot to mention that the above is a way to get "apply these substitutions unless these rules apply" behavior. It may be slightly simpler to instead only apply the substitution when you want... I believe you can do this by changing your composition lookup table's feature to "blank" (disabling it), and have your rules specify when to apply the lookup. This approach does not require the decomposition lookup table.

2
  • Thanks for the answer. Let me be sure I understand. First, there is no direct "ignore" statement way of doing this (which is primarily what the question was asking for). But second, it must be accomplished by proper coding of the lookups. In your example, I believe the capital letters would correspond to my "initial" version and the lower case letters would correspond to my "medial" version, correct? So you are making everything medial (which would actually not normally do anything as you state), then everything initial (capital), then if a letter precedes it, convert to medial (lower) again.
    – ScottS
    Mar 17, 2019 at 20:56
  • That's the approach, yes. Or at least, it's how I do it with ligatures - if there is an easier way I haven't discovered it yet! Mar 17, 2019 at 23:54
0

In your Back|Match|Ahead rule at Fontforge, do not assign lookup to Match class. This will create Ignore statement.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.