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 automatic by default. 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.
Creating 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 Isol
: 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 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 named xxxx
will appear, and we simply change its name to Isolated
. Then, we paste the the glyph names into the code field.
But there is one problem. The code looks 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
I.e., the names of the glyphs with a preceding slash (/
). But for the OpenType class, we need the names without slashes, and separated by wordspaces. So, press Cmd-Shift-F to bring up the search and replace function, and replace /
(slash) with
(space):
bq.Note: This search and replace trick does not work in systems before OS X 10.7 Lion. If you are running an older system, please do the replacing in a plaintext editor of your choice, and paste the result back into the class definition in Glyphs.
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
.
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.
One class definition remains to be made. We also need a class called AnyLetter
. Create it through the Plus menu in File > Font Info > Features. 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.
Okay, but how do we get the complete list of names for all letters in our font? Easy. Go back to Font View, make sure the search field is empty, and click on the Letter category, almost all the way to the top of the sidebar. If you have a multi-script font, and you want to keep this, e.g., to the Latin part of your font, you can Cmd-click on the Latin languages category further below, and thus further narrow down your selection. Now, again, we can select all glyphs, choose Copy glyph names from their context menu, paste it into the code of the AnyLetter
class, and replace the slashes with spaces.
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.
Positional forms as 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 Refresh button between the Minus and Compile buttons, 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 AnyLetter
and the AFDKO feature file syntax knows an ignore sub
statement. And this is how we write it:
ignore sub @AnyLetter @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 to the rescue!
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 @AnyLetter @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' @AnyLetter;
ignore sub @AnyLetter @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' @AnyLetter, @AnyLetter @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' @AnyLetter, @AnyLetter @Medial';
sub @Medial' by @Isolated;
} ISOLATED;
lookup INITIAL {
ignore sub @AnyLetter @Medial';
sub @Medial' by @Initial;
} INITIAL;
lookup FINAL {
ignore sub @Medial' @AnyLetter;
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!
There’s a script for that!
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 my 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 OpenType > Build positional calt feature somewhere in the Scripts menu. Have fun.