Ligature order
I’m working on a font that has multiple ligs, and I’m running into a problem. I have l_l and an e_l ligs, set up like so:
sub l l by l_l;
sub e l by e_l;
Ostensibly, the order should make the l_l come before the e_l, but this is what I get:

Any suggestions?





























9.Feb.2008 2.08pm
The problem is that ’el’ in the sequence comes before ’ll’, despite the order written in the feature code.
So that means that I need to write a contextual something or another that says, “If the string el is followed by the string ll, do NOT substitute the e_l ligature.”
I know it can be done, I just don’t know how to do contextual stuff.
9.Feb.2008 2.08pm
For now I’ve just eliminated the e_l lig, if only temporary.
9.Feb.2008 2.16pm
why liga and not clig?
9.Feb.2008 2.20pm
It has nothing to do with contextual stuff. Just replace the l with another l (that looks the same) so that when ell occurs l_l is ’taken’ and e_l can no longer be processed.
If you want, I can send you the thing I’m working on right now. It covers more or less the same thing.
9.Feb.2008 2.43pm
That would be cool. dan at dangayle dot com
But the explanation belowy tells why I’ll keep looking:
@david hamuel
why liga and not clig?
Honestly, I don’t know how to use that feature. But I have a semi- script type with about 100 ligs going on, some with alternates, so it might be handy to know. I’ve already ran into two situations like this, so a better solution is needed that won’t require multiple redundancies.
9.Feb.2008 2.51pm
> I don’t know how to use that feature. But I have a semi- script type with about 100 ligs going on, some with alternates, so it might be handy to know
Bickham Script Pro (feature file sample):
http://www.adobeforums.com/webx/.3bb7c828/0
9.Feb.2008 3.02pm
Dan, ygm. I made a new (FontLab) doc with just your liga thing. I think it works.
9.Feb.2008 3.06pm
** edit ** double post
9.Feb.2008 3.15pm
Although Jos’ approach also works, Dan is quite right to say that this can be solved by contextual code, too. Personally, I’d consider that simpler and more elegant than adding an extra glyph, though both approaches work.
So, you want the l_l ligature to take precedence over the e_l ligature. There are probably at least three ways to do this in AFDKO/FontLab code. Here’s one:
ignore sub e’ l’ l
sub e l by e_l
sub l l by l_l
(Note: I haven’t time to test this right now, but I *think* it should have the desired effect.
Cheers,
T
9.Feb.2008 3.31pm
Thomas, that would be an elegant solution. I was not familiar with this ’ignore command’ so I’ve tried this, but I really can’t get it to work.
9.Feb.2008 4.34pm
You do not necessarily need contextuality. You could as well use two (or more) lookups to get the same effect:
In a first step, create a lookup explicitly and put your substitution statements into it. In the example below, this would be the “thenThese” lookup. If, in your tests, you find that particular statements should have priority but are not executed because others interfere, create another lookup above the existing one, then place the higher-priority substitutions in it. In the example, this is the “theseFirst” lookup. I think two or a maximum of three lookups should do, it depends how complex matters are in your font:
lookup calt {
lookup theseFirst {
sub l l by l_l;
} theseFirst;
lookup thenThese {
sub e l by e_l;
} thenThese;
} calt;
Your test word suggests that these ligatures are essential part of the design rather than optional, i.e. there is no need to allow a user to switch them on/off. In so far ’calt’ is a good choice. Also, ’clig’ is one of the features which different applications may handle differently (either in a UI option of its own, or bundled with another feature in a common UI option), so it may be safer to avoid such features where possible. (This is a personal comment. Others may disagree.)
What’s missing in the ’ignore’ example is that following substitutions need to be contextual too:
lookup calt {
ignore sub e’ l’ l;
sub e’ l’ by e_l;
sub l’ l’ by l_l;
} calt;
(This is one of my most frequent errors ...) However, for some reason this still doesn’t work in the FLS preview. Haven’t tested with a compiled font yet.
9.Feb.2008 4.56pm
Ok, got it. Despite the ignore statement you’d still need two separate lookups:
feature calt {
lookup standardWithExclusion {
ignore sub e’ l’ l;
sub e’ l’ by e_l;
} standardWithExclusion;
lookup highPriority {
sub l’ l’ by l_l;
} highPriority;
} calt;
Note that in contrast to the first example in my last post, the lookup with standard substitutions is the first (not last) one. First, the ignore statement in the first lookup makes sure that e l is not substituted by the e_l ligature if another l follows. Then, the second lookup is applied to the text string which still contains individual l l and can substitute these by the l_l ligature.
While the ignore statement can be pretty powerful, in your case it complicates matters more than necessary because for every substitution in the “highPriority” lookup, you would need to add an according ignore statement to the “standardWithExclusion” lookup.
Karsten
9.Feb.2008 5.09pm
Wow. Karsten, I have NO IDEA what any of that means :)
Well, I understand a little. I just don’t understand the why.
9.Feb.2008 6.46pm
The first example,
lookup theseFirst {
sub l l by l_l;
} theseFirst;
lookup thenThese {
sub e l by e_l;
} thenThese;
worked.
The others wouldn’t compile, giving me:
9.Feb.2008 8.24pm
I can’t begin to write code like you guys. But one tiny note: While “calt” is on by default, with InDesign, anyway, I believe you can turn it off, using the paragraph styles > OT features menu. If you turn it off with the basic paragraph, all paragraph styles based on that will also have calt off.
Still, it is usually a good idea to make the comp take a conscious action rather than to depend on him/her to *remember* to take action. If you as the designer feel they should be on, put them in calt & make the comp do work to not use them.
10.Feb.2008 2.30am
In the code, you need to replace quoteright/quoteleft by simple quotes. Typophile is just too smart ...
Indeed, while ’calt’/’liga’ are currently the best choices for substitutions to be on by default, they still can be switched off. Oh, and I must have been sleeping yesterday: since no contextuality is involved, ’liga’ is it.
10.Feb.2008 3.02am
In OpenType Layout, each font has a list of little procedures called lookups. Those lookups are assigned to layout features. Typically, in simple Western OpenType fonts, one lookup is assigned to just one feature. But you also can have several lookups being assigned to one feature, and also one lookup being assigned to several features.
It is important to realize that there are two separate lists of things in the font: one is a list of lookups and the other is a list of features (there is also the issue of languagesystems but I won’t discuss it here). And then there are just mappings between those. The Adobe FDK for OpenType (AFDKO) syntax for writing OpenType Layout feature definitions, used by AFDKO itself as well as FontLab Studio and DTL FontMaster, hides this fact and makes you believe that you define the “action code” inside of feature definitions:
# AFDKO 1.6 / FontLab Studio 5 syntaxfeature liga {sub l l by l_l;
sub f i by f_i;
} liga;
feature swsh {sub f by f.swsh;
} swsh;
This is a simplified notation of the following:
# AFDKO 1.6 / FontLab Studio 5 syntaxfeature liga {lookup L1 {
sub l l by l_l;
sub f i by f_i;
} L1;
} liga;
feature swsh {lookup L2 {
sub f by f.swsh;
} L2;
} swsh;
So here, we have one lookup
liga1that is assigned to theligafeature. If one lookup is assigned to just one feature, the AFDKO feature syntax allows omitting thelookupstatement. But it is important to realize that it is still there, implicitly.Actually, a more precise representation of how this information is represented in the font would be the following:
# AFDKO 2.0-only / future FontLab Studio 6-only syntax# Lookupslookup L1 {sub l l by l_l;
sub f i by f_i;
} L1;
lookup L2 {sub f by f.swsh;
} L2;
# ...# Features
feature liga {lookup L1;
} liga;
feature swsh {lookup L2;
} swsh;
The lookup names (L1, L2) do not matter because they are not written into the font. Internally, lookups are just identified by numbers. You also can have several lookups being assigned to one feature:
# AFDKO 1.6 / FontLab Studio 5 syntaxfeature liga {lookup L1 {
sub l l by l_l;
sub f i by f_i;
} L1;
lookup L3 {
sub e l by e_l;
} L3;
} liga;
Internally, this would be written into the font as:
# AFDKO 2.0-only / future FontLab Studio 6-only syntax# Lookupslookup L1 {sub l l by l_l;
sub f i by f_i;
} L1;
lookup L3 {sub e l by e_l;
} L3;
# Featuresfeature liga {lookup L1;
lookup L3;
} liga;
You can also have one lookup being assigned to several features:
# AFDKO 1.6 / FontLab Studio 5 syntaxfeature liga {lookup L1 {
sub l l by l_l;
sub f i by f_i;
} L1;
} liga;
feature calt {lookup L1;
} calt;
This corresponds to the internal structure:
# AFDKO 2.0-only / future FontLab Studio 6-only syntax# Lookupslookup L1 {sub l l by l_l;
sub f i by f_i;
} L1;
# Featuresfeature liga {lookup L1;
} liga;
feature calt {lookup L1;
} calt;
In text formatting using OpenType Layout, each line of text is divided into “runs” of text that have the same formatting (i.e. font and combination of enabled OpenType Layout features) and directionality. In a typical European text, each line of text is just one “run”, but if you have Arabic or Hebrew with interspersed European numerals or European phrases, those would be separate runs.
Each run has a certain combination of features that will be applied to it (controlled by the user, e.g.
dlig, as well as those that the layout engine applies automatically, e.g.ccmp). When the text is being displayed, each run is typeset using the default glyphs representing the text’s characters (i.e. the glyphs that have Unicode codepoints assigned for those characters). Then, for some Asian scripts, special glyph reordering is being done (but not for European scripts). And then, the list of lookups is retrieved for the combination of features that are being applied.So if I have a font with the OpenType Layout code defined as follows:
# AFDKO 2.0-only / future FontLab Studio 6-only syntax# Lookupslookup L1 {sub l l by l_l;
sub f i by f_i;
} L1;
lookup L2 {sub f by f.swsh;
} L2;
lookup L3 {sub e l by e_l;
} L3;
# Featuresfeature liga {lookup L1;
lookup L3;
} liga;
feature swsh {lookup L2;
} swsh;
feature calt {lookup L1;
} calt;
And the features
liga,swshandcaltare all applied to the text, then the OpenType Layout engine retrieves the list of the lookups associated with all those features (i.e.L1,L2andL3), and applies them.The lookups are applied one after another to the entire run of text in the sequence the lookups have been defined in the font. So in the above example, first the lookup
L1is applied to the default string of glyphs (because it is assigned to theligaandcaltfeatures). Let’s assume that the user typesets the following text:Hellenic figure
So the default series of glyphs in the run would be:
H e l l e n i c space f i g u r eThe lookup
L1is “executed” on the run. It replaces the glyph sequencel lwith the glyphl_land it replaces the glyph sequencef iwith the glyphf_i. So after the lookup as been applied, the glyph run looks like this:H e l_l e n i c space f_i g u r eThen, the lookup
L2referenced by theswshis applied to the glyph run. It replaces the glyphfwith the glyphf.swsh. This does not produce any change in the text because the glyphfis not present in the glyph run anymore (as theL1lookup replaced it).Finally, the lookup
L3is executed. It replaces the glyph sequencee lwith the glyphe_l. This does not produce any change either, because the glyph sequencee lis not in the glyph run (we have the glyph sequencee l_l, but that’s completely different).So the final product of the processing is:
H e l_l e n i c space f_i g u r eWhat if I change the order of the lookups defined in my font?
# AFDKO 2.0-only / future FontLab Studio 6-only syntax# Lookupslookup L2 {sub f by f.swsh;
} L2;
lookup L3 {sub e l by e_l;
} L3;
lookup L1 {sub l l by l_l;
sub f i by f_i;
} L1;
# Featuresfeature liga {lookup L1;
lookup L3;
} liga;
feature swsh {lookup L2;
} swsh;
feature calt {lookup L1;
} calt;
Then, the lookup
L2will be first applied to my default glyph runH e l l e n i c space f i g u r e. It replaces the glyphfwith the glyphf.swsh, so after the first lookup has been applied, the glyph run looks like the following:H e l l e n i c space f.swsh i g u r eNow the lookup
L3is applied, changing the glyph sequencee linto the glyphe_l, so the result is:H e_l l e n i c space f.swsh i g u r eFinally, the lookup
L1is executed. It tries to replace the glyph sequencel lwith the glyphl_land the glyph sequencef iwith the glyphf_i. None of those replacements will be carried out because those glyph sequences are no longer in the glyph run. So the final result of our processing is:H e_l l e n i c space f.swsh i g u r eOf course if only some feature were applied, e.g. only
caltor onlycaltandligabut noswsh, only the appropriate lookups would be executed — but always in the sequence in which they are defined in the font.Note that in FontLab Studio, you cannot currently define the lookups outside of feature definitions. The lookups are defined inside of the feature definitions — explicitly using the
lookupkeyword or implicitly by just writing the substitution statements. When the AFDKO code is compiled into binary OpenType Layout tables, the lookups are compiled and stored in the font in the sequence they have been defined in the syntax, i.e. in the sequence the feature have been defined. So by reordering the features in the OpenType panel of FontLab Studio you actually change the order in which the lookups will be stored in the font.This is extremely important for feature interaction. If you have one lookup that replaces the
fglyph with the smallcapf.smcpglyph (this lookup is mapped to thesmcpfeature), and you have a lookup that replaces the glyph sequencef iby the glyphf_i(that lookup is used by theligafeature), you want to make sure that, when both features are applied, the smallcap lookup is executed first. Otherwise, you’d end up with a lonelyf_iligature sticking out of an all-smallcaps text. And the FontLab Studio method to make sure that the smallcap lookup is executed first is to place thesmcpfeature definition before theligafeature definition in the OpenType panel.The task of writing good OpenType Layout feature definitions becomes somewhat challenging when you have complex feature definitions that involve many lookups, and you have a potentially large number of different interacting features.
I recommend reading this article by John Hudson for further understanding of how OpenType Layout works:
http://www.microsoft.com/typography/Glyph%20Processing/intro.mspx
Regards,
Adam
10.Feb.2008 8.37am
[Removed this post — and changed its position ... — since Adam’s description is much more detailed and exact. What I keep is the link to the Feature File Syntax.]
10.Feb.2008 8.57am
Wowsers.
Thanks Adam and Karsten!
So, if I have everything right, aside from cutting and pasting Karsten’s code above, I should do multiple lookups under the same feature. Then each lookup is applied to the string of text in the order that the lookup is created.
So without cheating by looking, this is how I would accomplish my goal as set out above:
feature liga {lookup L1 {sub l l by l_l;} L1;lookup L2 {sub e l by e_l;} L2;} liga;No need to worry about contextuality then, since this definition makes
l_ltake precedence overe_lat all times. This is very helpful. Thanks again guys!11.Feb.2008 8.05am
Dan, why not just create an e_l_l ligature as you like it and put it first in order.
Stephen