Creating Fonts with Inkscape and FontForge | Part#7
<Part#6> - Table of Contents - <Part#8>
Part#7 - Vertical Abugida
In this tutorial, we design a vertical abugida with three consonants an two vowels.
The rules are as follows: preview
- Written vertically from left to right.
- Consonants have final/initial/isolated (when they're alone) and medial forms.
- Vowels go to the right of the consonant right before them, and to the left of the consonant just after them.
- If there isn't enough space of if a vowel is alone, a special carrier is added that can support up to three vowels.
-
Vertical writing in LibreOffice
- Go to
Tools| Options|Language settings|LanguagescheckDefault Language for documents|Asian:Japanese (or chinese)and validate. - This added a new setting in
Format|Page...|Text-Direction->Left-to-right(vertical). - You can now type vertically.
- Unfortunately as you might notice, only Chinese or Japanese characters will be correctly displayed vertically. Latin letters will always be like this: Imgur
- So, even though our script is vertical, we will have to design it horizontally.
- Another solution would be to put our glyphs somewhere in the range reserved for Chinese or Japanese characters, but we would need a program to convert what we type in this new range. Simply mapping [a,b,c,...]->[禁,庭,平,...] with substitutions as we did [A-Z]->[a-z] will not work here, because LibreOffice only sees the glyphs you type into it, and the rendering is handled separately. So as far as it is concerned, you are typing Latin letters and it should be horizontal anyway.
- If you are still interested in doing it this way, you will need to enable
has vertical metricsinElement|Font Info...|General, then doMetrics|Set vertical advance|set to the height of your glyphsand it will work. You will need to set this value manually for each glyph without nice a way of previewing it, however. (On top of having to type in Chinese with your font.)
- Go to
-
Create a font project named "Font#7"
-
Tracing the glyphs
- Set the page of Inkscape to 1800px in width and height, and add a grid with spacing 100px.
- Trace the following glyphs using the spiro tool with stroke width 50px with rounded joints: screen. Make sure that the ends of the strokes are perfectly aligned horizontally as the letters will be stacked against one another. (Notice that we will need three sets of vowels that will stack differently on the glyph before them).
-
Importing the letters in FontForge
-
Set ascent to 900, descent to 900
-
Convert the spiro paths
Path|Object to paththenPath|Stroke to pathbefore importing them in FontForge. You can combine elements withPath|Union, it can also resolve issues with glyphs not being imported correctly. Here's what it should look like: screen.
Here's tip i gave in Part#1, you will need to use it to ensure that the letters you import are perfectly centered like mine, or you could manually drag them on the baseline:Tip: If you had done precise positioning in Inkscape and don't want to lose it, copy the letter AND a small rectangle like this before copy pasting it, then delete the rectangle from FontForge and voila!
-
Import all the letters, you will have to add an encoding slot for the "carrier", its variations but also "a.left", "a.right", ...
-
Set the bearings to -25.
-
Create the "space" with
Metrics|Set widthto 800. -
Also create the letters A,B,C,D,E using "set width", the value does not matter.
-
-
Adding the anchors
-
We will be adding mark-to-base anchors as in tutorial 3.
-
First let's create the three anchor classes. To do this, open
Element|Font Info...|Lookups|panel GPOS->Add Lookupof type "Mark to Base Position" with a <New> feature "mark" and rename it "mark-to-base". -
Select the lookup you just created and "Add Subtable" with name "center" and a <New Anchor Class> center. Validate, then add a second subtable "left" with anchor class "left", and finally another named "right" with anchor class "right".
Even though FontForge proposes it to you, do not put all your anchor classes inside the same subtable. Always create a separate subtable for each one.
-
Open a glyph by clicking on it, then right-click
Add Anchor. The vowels will need anchors of type "Mark" and the consonants&carriers will need anchors of type "Base Glyph". -
Add an anchor of class "left" below all consonants&carriers, and of class "right" above them.
-
The carriers will need a third base anchor of class "center" at their centers.
-
Finally, add a mark anchor of class "left" above each vowel named "x.left", one of class "right" below each vowel named "x.right" and one of class "center" to the center of all default vowels (a&e).
-
Here's the result: Imgur
you can go to
Element|Font Info...|lookup|pane GPOS, open the "mark-to-base" lookup, double-click one of the sublookup, select the anchor class and do "Anchor Control" as in Part#3 to position all the marks. Simply drag the blue star to do so. You may select either mark or base in the list in the top left hand corner, or click one of the pairs displayed on screen.
-
-
Inserting the carrier
-
Remember that the first vowel goes inside the carrier, the second goes above it, the third goes below it.
The parentheses show which vowels stack on which carrier.
bis a consonant;
ais a vowel;
*is a carrier;
'marks the current vowel in the stream, the one that we can replace. -
We need to insert a carrier in the following situations :
a|bstays the samea|a|bbecomes(*|a|a)ba|a|a|bbecomes(*|a|a|a)bb|a|a|a|bbecomesb|a(*|a|a)bb|a|a|a|a|bbecomesb|a(*|a|a|a)bb|a|a|a|a|a|bbecomesb|a(*|a|a|a)a|bb|a|a|a|a|a|a|bbecomesb|a(*|a|a|a)(*|a|a)bb|a|a|a|a|a|a|a|bbecomesb|a(*|a|a|a)(*|a|a|a)b- ...
-
Therefore, the rules for inserting carriers are as follow. We proceed by elimination:
- ignore
*|a' - ignore
*|a|a' - ignore
*|a|a|a' - ignore
*|a|a|a|a'|b - ignore
a'|b - ignore
b|a' - replace
a'bycarrier|a
- ignore
-
Here's the code for it:
<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;lookup INSERT {
sub a by carrier a;
sub e by carrier e;
} INSERT;feature liga {
lookup InsertCarrier {
@b = [b c d];
@a = [a e];
@c = [carrier];
ignore sub @c @a';
ignore sub @c @a @a';
ignore sub @c @a @a @a';
ignore sub @c @a @a @a @a' @b;
ignore sub @a' @b;
ignore sub @b @a';
sub @a' lookup INSERT;
} InsertCarrier;
} liga;
<code end>This code uses chaining contextual substitution. I recommend that you read about it from the reference linked above. Basically, lookups can only have a single type. This is an issue because multiple substitutions, the kind that allow us to do
sub a by b c;is of a different type from contextual substitutions, which allows us to target specific sequences by looking backward an forward.
One way to go around this limitation is to first use contextual substitution to mark a character with an alternatesub a b' d by b.mark;, then use a second lookup to unpack the "b.mark"sub b.mark by b c;.
However, this requires us to create another set of glyphs called "x.mark". A better way is chaining contextual substitution. For example, whatsub b a' lookup INSERT c;says is to apply the lookup INSERT to the letteraonly if it is followed by acand preceded by ab. You can mix lookups of different types this way.
If your lookup takes two inputs, e.glookup EG { sub a b by a_b; };then the chaining rulesub [d e] a' lookup EG b' [d e];says to replaceaandbby the ligaturea_bonly if they are preceded or followed by andeord.
Finally, note how we defined the extra lookup outside of the liga feature, so that it does not modify the glyph stream.
-
-
Selecting the right vowel
-
We need to replace
abya.leftora.rightdepending on where it is. -
Here are the rules:
- replace
*|a|a'bya.right - replace
*|a|a.right|a'bya.left. Don't forget to account for the updated stream! - replace
b|a'bya.right - replace
a'|bbya.left
- replace
-
Here's the code for it:
<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;lookup INSERT {
sub a by carrier a;
sub e by carrier e;
} INSERT;feature liga {
lookup InsertCarrier {
@b = [b c d];
@a = [a e];
@c = [carrier];
ignore sub @c @a';
ignore sub @c @a @a';
ignore sub @c @a @a @a';
ignore sub @c @a @a @a @a' @b;
ignore sub @a' @b;
ignore sub @b @a';
sub @a' lookup INSERT;
} InsertCarrier;
lookup SelectVowel {
@b = [b c d];
@a = [a e];
@a.left = [a.left e.left];
@a.right = [a.right e.right];
@c = [carrier];
sub @c @a @a' by @a.right;
sub @c @a @a.right @a' by @a.left;
sub @b @a' by @a.right;
sub @a' @b by @a.left;
} SelectVowel;
} liga;
<code end>Before re-importing the feature file, you need to delete the lookups it created in the pane GSUB as explained in Part#4:4.3.1
-
-
Placing the left vowels
-
"Mark to base" works so well because one base letter can be combined with multiple diacritic marks. However, it requires the diacritic marks to be entered directly after the base letter. The sequence b|a.right will work but a.left|b will not since a base letter cannot combine with a diacritic mark that comes before it. To make it work we need to switch their order.
-
So how do we deal with the following sequence
c|a|a.right|a.left|a.left|b? This is an example of whata|a|a|a|bwould become with our current lookups. The firsta.leftwill stack correctly on the carrier because it was inserted there, but in order to stack the lasta.lefton theb, the only way is to somehow exchange their positions:c|a|a.right|a.left|b|a.left. -
The first rule of this new lookup will be to
ignore sub @c @a @a.right @a.left';because we want to keep the firsta.lefton the carrier always. -
The second rule will need to do something like
sub @a.left @b by @b @a.left;. This is impossible however. There is no such lookup type. It is again a mix of a contextual substitution and a multiple substitution. -
One solution would be to do a ligature first :
sub @a.left @b by @b.ligathen unpack it in reverse:sub @b.liga by @b @a.left;. However, we would need to create yet more empty glyphs named "x.liga" to do so. -
Therefore, we will need to use chaining substitution again. Here's the code for it ; it was made more compact by defining the classes globally and putting some of the new lookups on a single line. It's pure logic so I'll let you figure it out, it souldn't be too hard if you've read the Adobe Reference. There were probably other ways to achieve this as always:
<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;@b = [b c d];
@a = [a e];
@c = [carrier];
@a.left = [a.left e.left];
@a.right = [a.right e.right];lookup INSERT {
sub a by carrier a;
sub e by carrier e;
} INSERT;lookup RIGHTCOPY {
sub @a.left' b by b;
sub @a.left' c by c;
sub @a.left' d by d;
} RIGHTCOPY;lookup ALEFT { sub @b by a.left; } ALEFT;
lookup ELEFT { sub @b by e.left; } ELEFT;feature liga {
lookup InsertCarrier {
ignore sub @c @a';
ignore sub @c @a @a';
ignore sub @c @a @a @a';
ignore sub @c @a @a @a @a' @b;
ignore sub @a' @b;
ignore sub @b @a';
sub @a' lookup INSERT;
} InsertCarrier;
lookup SelectVowel {
sub @c @a @a' by @a.right;
sub @c @a @a.right @a' by @a.left;
sub @b @a' by @a.right;
sub @a' @b by @a.left;
} SelectVowel;
lookup PlaceLeftVowel {
ignore sub @c @a @a.right @a.left';
sub a.left' lookup RIGHTCOPY @b' lookup ALEFT;
sub e.left' lookup RIGHTCOPY @b' lookup ELEFT;
} PlaceLeftVowel;
} liga;
<code end>
-
-
Initials, finals and isolated
-
This code is standard besides the need to accout for eventual vowels stacking on an initial/final or isolated; there is nothing to explain here. Notice how I define some glyph classes by agreggating other classes (e.g @VOWL, @ALL and @CONS):
<code begin>
languagesystem DFLT dflt;
languagesystem latn dflt;@b = [b c d];
@a = [a e];
@c = [carrier];
@a.left = [a.left e.left];
@a.right = [a.right e.right];@VOWL = [@a @a.left @a.right];
@CONS = [@b @c];
@CONS.isol = [b.isol c.isol d.isol carrier.isol];
@CONS.fina = [b.fina c.fina d.fina carrier.fina];
@CONS.init = [b.init c.init d.init carrier.init];
@ALL = [@a @a.left @a.right @CONS @CONS.isol @CONS.fina @CONS.init];lookup INSERT {
sub a by carrier a;
sub e by carrier e;
} INSERT;lookup RIGHTCOPY {
sub @a.left' b by b;
sub @a.left' c by c;
sub @a.left' d by d;
} RIGHTCOPY;lookup ALEFT { sub @b by a.left; } ALEFT;
lookup ELEFT { sub @b by e.left; } ELEFT;feature liga {
lookup InsertCarrier {
ignore sub @c @a';
ignore sub @c @a @a';
ignore sub @c @a @a @a';
ignore sub @c @a @a @a @a' @b;
ignore sub @a' @b;
ignore sub @b @a';
sub @a' lookup INSERT;
} InsertCarrier;
lookup SelectVowel {
sub @c @a @a' by @a.right;
sub @c @a @a.right @a' by @a.left;
sub @b @a' by @a.right;
sub @a' @b by @a.left;
} SelectVowel;
lookup PlaceLeftVowel {
ignore sub @c @a @a.right @a.left';
sub a.left' lookup RIGHTCOPY @b' lookup ALEFT;
sub e.left' lookup RIGHTCOPY @b' lookup ELEFT;
} PlaceLeftVowel;
lookup Isolated {
ignore sub @ALL @CONS';
ignore sub @CONS' @CONS;
ignore sub @CONS' @VOWL @CONS;
ignore sub @CONS' @VOWL @VOWL @CONS;
ignore sub @CONS' @VOWL @VOWL @VOWL @CONS;
sub @CONS' by @CONS.isol;
} Isolated;
lookup Initials {
ignore sub @ALL @CONS';
sub @CONS' by @CONS.init;
} Initials;
lookup Finals {
ignore sub @CONS' @CONS;
ignore sub @CONS' @VOWL @CONS;
ignore sub @CONS' @VOWL @VOWL @CONS;
ignore sub @CONS' @VOWL @VOWL @VOWL @CONS;
sub @CONS' by @CONS.fina;
} Finals;
} liga;
<code end> -
Final result.
For some reason, trying to export as pdf from LibreOffice breaks the font when it is vertical, even though it renders just fine inside of it. Therefore, this sample was written horizontally and the pdf rotated afterwards. If you encounter this issue, you could also cheat by typing in landscape mode like this.
And as you can see, I didn't put much work into positioning the mark precisely...
-
PS: Just noticed that I made you create the glyphs for A,B,C,D,E but never use them. You could for example add
lookup NoCaps { sub [A-E] by [a-e]; } NoCaps;as the first lookup inside liga to allow typing in UpperCase with your font.
Posted Mar 9, 2018, 8:42 PM UTC · edited