Creating Fonts with Inkscape and FontForge | Part#4
<Part#3> - Table of Contents - <Part#5>
Part#4 - Feature Files & Substitutions
In this tutorial, we explore the many applications of substitutions using feature files.
Requirement : read the Opentype Cookbook to learn about features, lookups, and the "programming language" we will be using inside feature files.
Opentype defines multiple kinds of substitution : Single substitution, Multiple substitution, Alternate substitution, Ligature substitution, Contextual substitution, Chaining contextual substitution...
I personally do not know anything about those, I merely use them according to the Cookbook, and can tell what will and will not work from experience.
If you ever have a doubt, refer to this Adobe feature file syntaxe reference. It has a section on "Glyph substitution (GSUB) rules" with many examples.
First off, the rules of the script :
- There are 3 letters (a,b,c).
- When you type "ab", "abc", "ac", "acb", they are dynamically replaced with custom ligatures "a_b", "a_c", "a_b_c", "a_c_b".
- After that, if there are multiple "a", "b", or "c"s the repeated letters get replaced with a special glyph "a.repeat", "b.repeat", "c.repeat".
This is what we're going to implement in this tutorial. This tutorial is more about learning how to import feature files into FontForge than understanding them. I will try to explain how it all works, but I'm not sure how that turned out, so if you haven't done it yet, please read the Opentype Cookbook!
-
Create a font project named "Font#4"
-
Open Inkscape and draw some glyphs. I will be using the same grid as in Tutorial#1 to help position my curves. Here's what it should look like. (where x_x denotes the ligatures that we will create)
To do this, I used Bezier curves, and set their Stroke Style to 25px with round caps, then usedPath|Stroke to Pathto convert it into a shape that can be copied over to FontForge. -
In fontforge
-
Set Ascent/Descent (325px and 175px), Import the glyphs in fontforge.
There are no issues with a,b,c but how do you import a_b, a_c, ... ?
-
To import a_b for example, start by creating an empy glyph :
Encoding|Add Encoding Slots|1 ->OK, it should appear at the bottom of the font.
Tip: You can toggle
Encoding|compactto hide empty glyphs and make the font easier to work with.-
Right-click
Glyph Infoon the new glyph and set it's name to "a_b" -
Copy-paste the glyph from Inkscape like any other glyph.
-
Repeat for "a_c", "a_b_c", "a_c_b", "a.repeat", ... You can add multiple encoding slots at once, and delete them with
Encoding|Detach and remove glyphs. -
Set both bearings of all the glyphs to 20.
-
Add a space of width 200.
-
Precisely position the repeat glyphs. sceen.
-
-
Creating a basic feature file
-
In your project folder, create a file named "Font#4.fea" (of extension .fea) and edit it as a text file.
-
Add in this code :
<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;feature liga {
lookup Ligatures {
sub a b c by a_b_c;
sub a c b by a_c_b;
sub a b by a_b;
sub a c by a_c;
} Ligatures;} liga;
<code end>If you read the OpenType Cookbook linked in the introduction, you should understand this. Basically, the feature liga is where we will put our rules for ligatures, and different apps may choose whether to implement them or not. (Suposedly, mutliple features exist depending on what you're trying to achieve, but in practice, I will always put all my code inside liga features because it's more convenient – and it works)
Each time the user types a letter, the glyphs stream is updated and the whole thing is run again. Glyph streams are usually a line of text, for example it could contain the two words :a|b|a|b|c| |a|c|c|a|bthat you typed in your text editor. (Notice how there is a space between the words. Also, this is just a notation to separate the glyphs.)Then, this stream is passed to the first lookup lookup Ligatures. The lookup tries to apply all the rules I gave him to the first letter, then the second, ... on so on by moving over the stream one letter at a time. I will detail this for you :
The lookup sees
a``|b|a|b|c| |a|c|c|a|b. The first rulesub a b c by a_b_c;starts with an a, so it tries for a b next and finds it (here :a|b``|c|b|c| |a|c|c|a|b), it then tries to look forward once more, looking for a c.Unfortunately, the third letter is not a c but an a (here :
a|b|a``|b|c| |a|c|c|a|b), so the rule ends.It moves on to the next rule
sub a c b by a_c_b;this matches the first a, but the second letter is a b, not a c, so the rule ends as well.It tries the third rule
sub a b by a_b;, sees an a followed by a b.
Because this rule matches entirely, it gets applied and the glyph stream is modified accordingly toa_b``|a|b|c| |a|c|c|a|b(Note that a_b is a single glyph inside the new stream.)Then, the lookup moves forward by one glyph and tries to match all the rules again starting at :
a_b |``a``|b|c| |a|c|c|a|b.
We see that the rulesub a b c by a_b_c;is the first one to match the stream this time. Here's how :a_b|``a|b|c``| |a|c|c|a|b.Therefore, the three glyphs are replaced in the stream by a single glyph a_b_c :
a_b|``a_b_c``| |a|c|c|a|b.Then the lookup moves on to the next glyph, the space for which no rules matches, so it moves to the next and matches a,c, then moves on and matches nothing so the c remains a c, then it moves and matches a,b.
This is what the modified stream output by lookup Ligatures looks like :
a_b|a_b_c| |a_c|c|a_b.
As you can see, the order of the rules matters. Ifsub a b c by a_b_c;had been aftersub a b by a_b;then it would've never had a chance of being called, assub a b by a_b;would always be accepted before it. -
Save the feature file. To import it, you need to do this :
-
First, check
Element|Font Info|Lookups|pane GSUB. If there is anything there, select the first element in the list, holdshift, select the last one andDeleteeverything.This will be useful when testing your feature files, you will need to delete the current one before you can import the new one.
-
Exit this window.
-
Import the feature file :
File|Merge Feature Info... -> Font#4.fea -
Save and generate the font.
-
Result : This is what the font looks like when I write "a b c ab ac abc acb" in LibreOffice Writer. As you can see, the letters are combining correctly.
-
-
-
A more complex feature file
-
Dealing with capital : A,B,C
-
Consider the following glyph stream :
A|b|C|B|b. What we want is a lookup that will take this and convert it toa|b|c|b|bTHEN output it to lookup Ligature. This way, even though our font doesn't have capitals, we will be able to type with them. (Which is better than leaving white spaces when "ABC" is typed in as it does now). -
Here's the code to do this. I simply added a new lookup before lookup Ligatures. It should be self explanatory. The first lookup modifies the stream, then passes it to the second which modifies it further before it is displayed.
<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;feature liga {
lookup NoCaps {
sub A by a;
sub B by b;
sub C by c;
} NoCaps;lookup Ligatures {
sub a b c by a_b_c;
sub a c b by a_c_b;
sub a b by a_b;
sub a c by a_c;
} Ligatures;} liga;
<code end>Before importing this feature file, you need to create the glyphs A,B,C in your font or you will get an error. Personally, I simply
Metrics|Set Widththose empty glyph to create them, or simple copy paste something in them, you will never see them anyway. -
Before re-importing the feature file, you need to delete the lookups it created in the pane GSUB as explained above in 4.3.1.
-
Import the modified feature file and generate the font.
-
-
Deal with accented letters
-
Consider the following glyph stream :
à|Á|á|B|ĉ. What we want is a lookup that will take this and convert it toa|a|a|b|cTHEN output it to lookup Ligature. This way, even though our font doesn't support accents, it will always render correctly. (This is moot since we only have three letters, but let's do it anyway) -
Here's the code to do this. I simply added a new lookup before lookup NoAccents. It should be self explaining. (Note that we could've put NoCaps and NoAccent in a single lookup. Also notice the name of the glyphs à->agrave, ... You can read these names when selecting glyphs inside FontForge).
<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;feature liga {
lookup NoAccents {
sub agrave by a;
sub Aacute by a;
sub aacute by a;
sub ccircumflex by c;
} NoAccents;lookup NoCaps {
sub A by a;
sub B by b;
sub C by c;
} NoCaps;lookup Ligatures {
sub a b c by a_b_c;
sub a c b by a_c_b;
sub a b by a_b;
sub a c by a_c;
} Ligatures;} liga;
<code end> -
Before re-importing the feature file, you need to delete the lookups it created in the pane GSUB as explained above in 4.3.1.
Before importing the feature file, you need to create the glyphs A,B,C,à,Á,á,B,ĉ or you will get an error. Personally, I simply
Metrics|Set Widththose empty glyph to create them, or simple copy paste something in them, you will never see them anyway.
You will need to re-encode you font to unicode, because the glyph ĉ does not appear in the default subset (latin3 has all the letters as well if you want a minimal encoding). -
Import the modified feature file and generate the font.
-
-
(extra) Dealing with special characters
-
The German Eszett (
ßcalled "germandbls" in fonts) is a fancy character to representss. If you want to use your font with German text, but do not have a glyph for ß, one thing you can do is write a lookup to transform the following glyph streams|t|r|a|ß|eintos|t|r|a|s|s|e, 'unpacking' the ß.THIS IS JUST AN EXAMPLE, as there is no "s" in our font, this will only create an error if you try to import the following code. Of course, you can draw one to test this feature if you want. Don't forget to create the glyph "germandbls" as well.
-
This is what that code would look like.
<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;feature liga {
lookup Special {
sub germandbls by s s;
} Special;lookup NoAccents {
sub agrave by a;
sub Aacute by a;
sub aacute by a;
sub ccircumflex by c;
} NoAccents;lookup NoCaps {
sub A by a;
sub B by b;
sub C by c;
} NoCaps;lookup Ligatures {
sub a b c by a_b_c;
sub a c b by a_c_b;
sub a b by a_b;
sub a c by a_c;
} Ligatures;} liga;
<code end>
-
-
-
Deal with repeated letters
-
As explained in the rules : We want to replace the stream
a|b|b|b|c|cbya_b|b.repeat|b_c|c.repeat. -
We have a choice to add the repeat symbols before or after we add the ligatures. We will add the repeated symbols after, just in case we decide to add more ligatures later on.
-
For example, the ligature a_b_b which would requires
sub a b b by a_b_b;would become insteadsub a b b.repeat by a_b_b;because we would have to anticipate the fact that the repeated 'b' would've been replaced in the stream before. -
This is the code, refer tor the Cookbook for the syntax.
<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;feature liga {
lookup NoAccents {
sub agrave by a;
sub Aacute by a;
sub aacute by a;
sub ccircumflex by c;
} NoAccents;lookup NoCaps {
sub A by a;
sub B by b;
sub C by c;
} NoCaps;lookup Ligatures {
sub a b c by a_b_c;
sub a c b by a_c_b;
sub a b by a_b;
sub a c by a_c;
} Ligatures;lookup Repeat {
sub [a] a' by a.repeat;
sub [b a_b a_c_b] b' by b.repeat;
sub [c a_c a_b_c] c' by c.repeat;
} Repeat;} liga;
<code end> -
This will work for single repeated vowel but what about
a|a|a|a|a? Only the second 'a' will be replaced :a|a.repeat|a|a|a. The trick is to dosub a.repeat a' by a.repeat;afterwards to obtaina|a.repeat|a.repeat|a.repeat|a.repeat. We could do this in a separate lookup set after this one, or we could add 'a.repeat' tosub [a a.repeat] a' by a.repeat.and obtain this :<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;feature liga {
lookup NoAccents {
sub agrave by a;
sub Aacute by a;
sub aacute by a;
sub ccircumflex by c;
} NoAccents;lookup NoCaps {
sub A by a;
sub B by b;
sub C by c;
} NoCaps;lookup Ligatures {
sub a b c by a_b_c;
sub a c b by a_c_b;
sub a b by a_b;
sub a c by a_c;
} Ligatures;lookup Repeat {
sub [a.repeat a] a' by a.repeat;
sub [b.repeat b a_b a_c_b] b' by b.repeat;
sub [c.repeat c a_c a_b_c] c' by c.repeat;
} Repeat;} liga;
<code end> -
Before re-importing the feature file, you need to delete the lookups it created in the pane GSUB as explained above in 4.3.1.
-
Import the modified feature file and generate the font.
-
-
Posted Mar 1, 2018, 8:12 PM UTC · edited