Features, part 4: positional alternates
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
suffixInitial
for initial variations with a.init
suffixMedial
for the default medial letters without suffixesFinal
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.