Ligature order

Dan Gayle's picture

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?

Jos Buivenga's picture

** edit ** double post

Dan Gayle's picture

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.

Dan Gayle's picture

For now I've just eliminated the e_l lig, if only temporary.

david h's picture

why liga and not clig?

Jos Buivenga's picture

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.

Dan Gayle's picture

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.

david h's picture

> 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

Jos Buivenga's picture

Dan, ygm. I made a new (FontLab) doc with just your liga thing. I think it works.

Thomas Phinney's picture

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

Jos Buivenga's picture

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.

k.l.'s picture

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.

k.l.'s picture

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

Dan Gayle's picture

Wow. Karsten, I have NO IDEA what any of that means :)

Well, I understand a little. I just don't understand the why.

k.l.'s picture

[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.]

Dan Gayle's picture

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:

charles ellertson's picture

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.

k.l.'s picture

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.

twardoch's picture

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 syntax

feature 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 syntax

feature 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 liga1 that is assigned to the liga feature. If one lookup is assigned to just one feature, the AFDKO feature syntax allows omitting the lookup statement. 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

# Lookups

lookup 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 syntax

feature 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

# Lookups

lookup L1 {
sub l l by l_l;
sub f i by f_i;
} L1;

lookup L3 {
sub e l by e_l;
} L3;

# Features

feature liga {
lookup L1;
lookup L3;
} liga;

You can also have one lookup being assigned to several features:

# AFDKO 1.6 / FontLab Studio 5 syntax

feature 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

# Lookups

lookup L1 {
sub l l by l_l;
sub f i by f_i;
} L1;

# Features

feature 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

# Lookups

lookup 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;

# Features

feature liga {
lookup L1;
lookup L3;
} liga;

feature swsh {
lookup L2;
} swsh;

feature calt {
lookup L1;
} calt;

And the features liga, swsh and calt are all applied to the text, then the OpenType Layout engine retrieves the list of the lookups associated with all those features (i.e. L1, L2 and L3), 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 L1 is applied to the default string of glyphs (because it is assigned to the liga and calt features). 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 e

The lookup L1 is "executed" on the run. It replaces the glyph sequence l l with the glyph l_l and it replaces the glyph sequence f i with the glyph f_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 e

Then, the lookup L2 referenced by the swsh is applied to the glyph run. It replaces the glyph f with the glyph f.swsh. This does not produce any change in the text because the glyph f is not present in the glyph run anymore (as the L1 lookup replaced it).

Finally, the lookup L3 is executed. It replaces the glyph sequence e l with the glyph e_l. This does not produce any change either, because the glyph sequence e l is not in the glyph run (we have the glyph sequence e 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 e

What if I change the order of the lookups defined in my font?

# AFDKO 2.0-only / future FontLab Studio 6-only syntax

# Lookups

lookup 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;

# Features

feature liga {
lookup L1;
lookup L3;
} liga;

feature swsh {
lookup L2;
} swsh;

feature calt {
lookup L1;
} calt;

Then, the lookup L2 will be first applied to my default glyph run H e l l e n i c space f i g u r e. It replaces the glyph f with the glyph f.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 e

Now the lookup L3 is applied, changing the glyph sequence e l into the glyph e_l, so the result is:

H e_l l e n i c space f.swsh i g u r e

Finally, the lookup L1 is executed. It tries to replace the glyph sequence l l with the glyph l_l and the glyph sequence f i with the glyph f_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 e

Of course if only some feature were applied, e.g. only calt or only calt and liga but no swsh, 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 lookup keyword 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 f glyph with the smallcap f.smcp glyph (this lookup is mapped to the smcp feature), and you have a lookup that replaces the glyph sequence f i by the glyph f_i (that lookup is used by the liga feature), you want to make sure that, when both features are applied, the smallcap lookup is executed first. Otherwise, you'd end up with a lonely f_i ligature 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 the smcp feature definition before the liga feature 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

Dan Gayle's picture

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_l take precedence over e_l at all times. This is very helpful. Thanks again guys!

Stephen Rapp's picture

Dan, why not just create an e_l_l ligature as you like it and put it first in order.
Stephen

Syndicate content Syndicate content