Features, part 4: positional alternates

Tutorial
by Rainer Erich Scheichelbauer
en fr zh

25 July 2022 Published on 23 April 2014

What makes a script font really neat, is when it has a bunch of alternative glyphs for different positions, and the right OpenType features to trigger them. Find out how to do it right in this tutorial.

One of the main problems for script fonts and similar designs is that the letters adopt different shapes depending on their relative position inside a word. In total, we can differentiate four possible positions:

  • .init: initial forms at the beginning of a word
  • .medi: medial forms in the middle of a word
  • .fina: final forms at the end of a word
  • .isol: isolated forms for when a letter stands all by itself

The four-letter codes init, medi, fina, and isol are the names of the corresponding OpenType features. And you may have guessed it already: You can trigger the automatic creations of these four features by appending their names as suffix to the glyphs. E.g., if you have both adieresis and adieresis.init in your font, then Glyphs can automatically generate the init feature for you. All you need to do is click on the Refresh button (marked with a circled arrow) at the bottom left in File > Font Info > Features and re-export your font.

The trouble is only that very few applications make use of the features by default. For instance, in InDesign, you need to activate Automatic Positional Forms in the OpenType options. Many users still do not know how to handle OpenType fonts, and will send you support e-mails. After all, for them, the font they spent their hard-earned money on does not seem to work. The mere phrase ‘activate automatic positional forms’ can drive fear into the hearts of some people.

So, in order to save you a lot of time and nerves, we should find a way to have positional forms appear automatically. As it so happens, there is a way. Stay tuned.

Medial forms as defaults

Now, in this tutorial, I will make two assumptions: Firstly, I assume that your default glyphs are medial shapes. Secondly, I assume that there are all four variations for each letter. That means that there are default glyphs like a, b, c and so on, all with medial shapes, plus three variations with .isol, .init and .fina suffixes for each of them. If that does not apply to your design, you will have to adapt the code accordingly, but that will prove to be pretty easy after you have read through this tutorial.

OpenType classes

I think it is pretty clear. We need four classes:

  • Isolated for isolated glyphs with a .isol suffix
  • Initial for initial variations with a .init suffix
  • Medial for the default medial letters without suffixes
  • Final for the final forms carrying a .fina suffix

Let’s start with Isolated: This one is easy, we just need to type .isol into the search field in the bottom right corner in Font View. Glyphs will narrow down the selection to all letters that have .isol in their name. All we need to do now is select all displayed glyphs (Cmd-A), and choose Copy Glyph Names > Space Separated from the context menu.

Now, we have the glyph names of all .isol glyphs in our clipboard. So, we go to File > Font Info > Features, click and hold the Plus button in the lower left corner, and pick Class from the pop-up menu. A new OpenType class will appear, and we simply change its name to Isolated. Then, we paste the the glyph names into the code field.

Your class code should now look something like this:

A.isol B.isol C.isol D.isol E.isol F.isol G.isol H.isol I.isol J.isol K.isol L.isol M.isol N.isol O.isol P.isol Q.isol R.isol S.isol T.isol U.isol V.isol W.isol X.isol Y.isol Z.isol a.isol b.isol c.isol d.isol e.isol f.isol g.isol h.isol i.isol j.isol k.isol l.isol m.isol n.isol o.isol p.isol q.isol r.isol s.isol t.isol u.isol v.isol w.isol x.isol y.isol z.isol

Much better. For the remaining classes, our task is clear and easy: We copy and paste the Isolated class three times by clicking on the class name Isolated and pressing Cmd-C, then three times Cmd-V. You will see four classes named Isolated. Change the names of three of them to Initial, Medial, and Final. Then, click into the code of each new class, and replace the suffix .isol with the respective suffix, or with nothing in Medial. You can easily do that by pressing Cmd-Shift-F, which will bring up the search and replace function. Replace the .isol suffix with the respective suffix, or with nothing in Medial.

This way, all classes are in sync, i.e., they have the same amount and order of glyphs stored in them. This is important for the substitution code we are going to write in a minute.

And by the way, you can rearrange the classes by simply dragging their names in the sidebar. The order is not important for the features to work, but for you to keep your oversight.

One class definition remains to be made. We need a class called AllLetters, create it through the Plus menu in File > Font Info > Features, and make sure the generate Feature automatically checkbox is on. This way, we get the complete list of names for all letters in our font, also including all the positional forms. Why? Because, for instance, the initial feature will go something like this: Switch a default letter to its initial shape unless there is any letter in front of it.

Needless to say, the AllLetters class will be larger than the positional classes we created before. So, it cannot have the same amount and order of glyphs as these other classes.

Contextual alternates

What we need now is a Contextual Alternates feature, or calt, because what we want to do is substitute glyphs depending on their context. So, our first step will be to create that feature. Again, in File > Font Info > Features, click and hold the Plus button at the bottom left. But this time, choose Feature from the pop-up menu. A new feature called xxxx appears, its name already selected, so we can conveniently change it to calt, in lowercase letters:

Press the Return key to confirm. If you already have other features in your font, you may also click on the Update button, so Glyphs can properly re-order the features. As opposed to classes, the order of features does indeed matter. And unless you have a good reason not to, I recommend you follow the default sort order that Glyphs suggests.

Writing the feature code

Remember how I summarized the initial feature: ‘Switch a default letter to its initial shape unless there is any letter in front of it.’

Okay, let’s build it bit by bit. ‘Switch default letter to initial shape’ translates to substituting the Medial class for the Initial class. So, we can start out by writing this line:

sub @Medial by @Initial;

Remember, in the feature code, sub...by stands for substitute…with, and class names need to be preceded by at signs in order to be recognized as classes. And every rule needs to be terminated by a semicolon, otherwise it does not count.

So far so good. But the problem is: this will substitute all default letters (Medial) with their initial forms. And we want to do that only when there is no letter in front of it, or in other words, unless there is any letter in front of it. Or, again in other words, ignore the case where there is any letter in front of it. Sounds great, because we have a class called AllLetters and the AFDKO feature file syntax knows an ignore sub statement. And this is how we write it:

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

Click the Compile button to see if you got everything right.

I need to focus on two things here: Firstly, the ignore sub statement precedes the sub statements it is intended for. That means that all following sub statements only count if the ignore sub condition is not fulfilled. Tricky. We’re good for now, but it will cause us some headache later. Read on.

Secondly, look at the tick marks '. In the ignore sub rule, we state that the exception counts for all glyphs in the Medial class. And in the following sub rule, we need to mark it again. Well, I suppose, here it would be clear, because there is nothing else being substituted. But we still need to mark it because it doesn’t need to be the same class as in the ignore sub statement above. It could be two different classes sharing a few glyphs. Plus, it could be a more complex contextual substitution.

Let me stress that again: You need to tick-mark the respective glyph classes in both the ignore sub and the sub statement. If your ignore sub does not work for you, you probably forgot one or both markers. Don’t worry, happens to the best of us.

Let’s see if it works already. In a new Edit tab, just type n a couple of times, and from the Features pop-up menu in the lower left corner of the window, choose Contextual Alternates.

Yes! It works! So, in theory, we just need to do the same thing for Isolated and Final, right? Right?!

No, not quite. Being the smart girls and boys that we are, we remember that the ignore sub statement counts for everything that follows. And three ignore sub statements would definitely get in each other’s way. So what do we do?

Lookups

Actually, I did not tell you the whole truth before. An ignore sub does not count for everything that follows, but just for everything that follows in the same lookup.

Huh? Lookup? What’s a lookup? Well, within a lookup, you can summarize a couple of rules under a name, and if you like, conjure them up again later. So, we need to put everything we wrote so far into a lookup, and give it a name. This is how we do it:

lookup INITIAL {
    ignore sub @AllLetters @Medial';
    sub @Medial' by @Initial;
} INITIAL;

I chose to call this one INITIAL, for obvious reasons. The syntax is clear: lookup, followed by a name, followed by an open curly brace. Then, all the rules that need to be inside the lookup. You do not need to do it, but you can indent them with Cmd-] if you find it prettier that way. And unindent with Cmd-[ if you change your mind again. At the end, we need to close the curly brace again, repeat the lookup name, and terminate the lookup with a semicolon.

Now, I need to tell you another important thing: between lookup blocks, order counts. Just like it does between features. The font will go through this feature until it finds something appropriate for the current glyph, and it will not look any further than that.

This is why we need to put the lookup with the largest ignore sub first. In our case, this is the one for the isolated variations, because there we need to ignore the case that there is anything in front of it, as well as the case that there is anything behind it. That makes two ignore sub rules. So, do we write two ignore sub statements?

lookup ISOLATED {
  ignore sub @Medial' @AllLetters;
  ignore sub @AllLetters @Medial';
  sub @Medial' by @Isolated;
} ISOLATED;

Sure, we could, and it would work that way. Some people, however, prefer to line up the ignored substitution chains between commas, like this:

lookup ISOLATED {
   ignore sub @Medial' @AllLetters, @AllLetters @Medial';
   sub @Medial' by @Isolated;
} ISOLATED;

And I am one of those people. So, I will keep it like this, har har.

While we are at it, we might as well add the Final substitutions. This time, the lookup goes to the end:

lookup ISOLATED {
   ignore sub @Medial' @AllLetters, @AllLetters @Medial';
   sub @Medial' by @Isolated;
} ISOLATED;

lookup INITIAL {
   ignore sub @AllLetters @Medial';
   sub @Medial' by @Initial;
} INITIAL;

lookup FINAL {
   ignore sub @Medial' @AllLetters;
   sub @Medial' by @Final;
} FINAL;

Again, make super-sure you get all the tick marks right. We always want to mark the Medial class, both in the ignore sub and sub rules. After all, the Medial class contains our default glyphs, and it is them that need to be substituted whenever the occasion demands it.

Okay, push the Compile button. If you get an error, read carefully what the error message is. Did you forget a semicolon? Mistype a class name? Forget an at sign? If you cannot figure it out, there is a lengthy troubleshooting section in the Glyphs Handbook: look for the OpenType Feature Code section in the Error Handling chapter.

If your features do compile, we can test-run them in an Edit tab again. Here is what happens when I type n a few times again:

See the isolated n at the left, and three times after each other on the right, each time in a different shape: initial, medial, final. Cool, high five, for the win!

Useful scripts

So you have read through all of this, and you understand the nature of lookup blocks and ignore sub statements. I guess you need a break now.

And here it is: I wrote a script that injects positional code into calt. It analyzes your glyph structure, checks for the suffixes .init, .medi, .fina, and .isol. The best thing about it: It does not assume that your four positional groups are in sync. So the class and code structure will look slightly different, but the basic ideas are the same. If you run the script again, it will update classes and feature code.

Download the Glyphs Scripts repository from mekkablue's GitHub page. Place the scripts in the Scripts folder (Script > Open Scripts Folder, Cmd-Shift-Y), and reload scripts (with Opt key: Script > Reload Scripts, Cmd-Opt-Shift-Y). You will then find Features > Build positional calt feature somewhere in the Scripts menu. Have fun.


Update 2015-06-22: updated for Glyphs 2.
Update 2017-01-09: corrected typos, updated screenshots and clarified the AllLetters class (thx @Raseone).
Update 2019-10-30: added note about positional OT features.
Update 2022-07-25: updated title, related articles, minor formatting.