Ligature substitution sequencing not working

Eimantas Paškonis's picture

I have 3 ligatures: FU, TU and UT. The latter being more important than the rest.

Current situation:


Desired result:

The code (take note of the sequence):

What am I doing wrong?

agisaak's picture

I'm not sure why this isn't working given the order of your substitutions, but if you want to guarantee that U_T takes priority over T_U, you could always explicitly place the two in separate lookups, e.g.

feature dlig {

    lookup HigherPriority {
        sub U T by U_T;
    } HigherPriority;

    lookup LowerPriority {
        sub T U by T_U;
    } LowerPriority;

} dlig;

André

Eimantas Paškonis's picture

Here's the thing that I couldn't figure out: what are lookups inside features?

It's interesting that if I disable FU, the UT ligature gets the priority.
What's more interesting is that if I disable TU instead, nothing changes...

agisaak's picture

GSUB lookups are sets of substitutions of the same type which are invoked when a particular feature is activated. All of the substitutions in a particular lookup will be applied before any substitutions in subsequent lookups. Each opentype feature is associated with one or more lookups.

You don't need to explicitly declare lookups within your feature blocks since FontLab will automatically group your substitutions into lookups of the appropriate type (in this case it should combine all of your substitutions into a single lookup since they are all type 4 GSUB substitutions, i.e. ligature replacement rules).

However, sometimes it is advantageous to declare lookups yourself, and this might be one of those cases. You don't have to name your lookups like I have done above, but doing so allows you to refer to them in other feature blocks if needed.

see http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.htm... for more details.

André

Stephen Rapp's picture

The f_U comes in first because of what André said. The GSUB looks for the first applicable sub in the text chain, so F U even though its further down the feature code list is the first instance of a substitution. If you have conflicting usages like this with ligature sets a simpler solution might be to create a longer ligature and put that before the others in code.
So like:
sub F U T U by F_U_T_U;
sub F_U by F_U;

You could break it up however works best for you, but a longer ligature ahead of the rest should repair any conflicts.
Stephen

twardoch's picture

Minerva,

Lookups are little procedures that are defined as a list, and then features refer to certain lookups. Multiple features can refer to the same lookup, and one feature can refer to multiple lookups. Lookups are always processed in the order they are defined.

Each lookup processes a glyph run from the beginning (for simplicity, let’s assume that a glyph run is a line of text). Within one lookup, the OpenType Layout engine looks for one best (longest) match, executes it and it proceeds to the next glyph and looks for the best match again — until the end of the glyph run. Then the next lookup is processed, with the resulting glyph run from the previous lookup as input.

Features always refer to lookups. In the AFDKO syntax, there is a simplification that allows you to write:

feature dlig {
# (all other substitutions)
sub U T by U_T;
sub F U by F_U;
sub T U by T_U;
# (all other substitutions)
} dlig;

but what it actually means is:

feature dlig {
lookup dlig1 {
# (all other substitutions)
sub U T by U_T;
sub F U by F_U;
sub T U by T_U;
# (all other substitutions)
} dlig1;
} dlig;

or, more precisely (using AFDKO 2.5 syntax which is not implemented in FontLab Studio 5.0 but is implemented in Fontographer 5 and in the upcoming FontLab Studio 5.1):

lookup dlig1 {
# (all other substitutions)
sub U T by U_T;
sub F U by F_U;
sub T U by T_U;
# (all other substitutions)
} dlig1;

feature dlig {
lookup dlig1;
} dlig;

The simplified AFDKO syntax blurs that reality, but it’s good to remember that that’s how it works: lookups are defined separately from features, and features refer to lookups. In other words, lookups define what happens with different portions of the glyph run and in which order, and features define when it should happen (i.e. under which circumstances, e.g. when the user expresses a certain typographic preference). Yet another method of looking at this would be to say that features provide the “user interface” to the OpenType Layout mechanism in the font, and lookups provide the “implementation”.

So, if you have the glyph run /F/U/T/U/R/E and your “dlig” feature refers to just one lookup (as shown above), the OpenType Layout engine proceeds like this:

1. Start processing lookup “dlig1” referred by feature “dlig” with input glyph run /F/U/T/U/R/E:
/F/U/T/U/R/E — no match
/F/U/T/U/R — no match
/F/U/T/U — no match
/F/U/T — no match
/F/U — match! substitute /F/U by /F_U, and proceed to the next unprocessed glyph
/T/U/R/E — no match
/T/U/R — no match
/T/U — match! substitute /T/U by /T_U, and proceed to the next unprocessed glyph
/R/E — no match
/R — no match, proceed to the next unprocessed glyph
/E — no match, end processing lookup

2. End processing lookup “dlig1”, resulting glyph run is /F_U/T_U/R/E

3. Start processing further active lookups with the resulting glyph run (there is none, but there would be if e.g. “lnum” were active as well).

4. End processing: resulting glyph run is /F_U/T_U/R/E.

If you want some substitutions happen earlier, you need to define it in a separate lookup earlier in the feature definition code — either by using the AFDKO keyword “lookup” or by defining it in a separate feature that is placed earlier.

If you write your code as:

feature dlig {
lookup dlig1 {
sub U T by U_T;
} dlig1;
lookup dlig2 {
# (all other substitutions)
sub F U by F_U;
sub T U by T_U;
# (all other substitutions)
} dlig2;
} dlig;

or (using AFDKO 2.5 syntax) as:

lookup dlig1 {
sub U T by U_T;
} dlig1;
lookup dlig2 {
# (all other substitutions)
sub F U by F_U;
sub T U by T_U;
# (all other substitutions)
} dlig2;

feature dlig {
lookup dlig1;
lookup dlig2;
} dlig;

then your input glyph run /F/U/T/U/R/E will be processed as follows:

1. Start processing lookup “dlig1” referred by the “dlig” feature with input glyph run /F/U/T/U/R/E:
/F/U/T/U/R/E — no match
/F/U/T/U/R — no match
/F/U/T/U — no match
/F/U/T — no match
/F/U — no match
/F — no match, proceed to the next unprocessed glyph
/U/T/U/R/E — no match
/U/T/U/R — no match
/U/T/U — no match
/U/T — match! substitute /U/T by /U_T and proceed to the next unprocessed glyph
/U/R/E — no match
/U/R — no match
/U — no match, proceed to the next unprocessed glyph
/R/E — no match
/R — no match, proceed to the next unprocessed glyph
/E — no match, end processing lookup

2. End processing lookup “dlig1”, resulting glyph run is /F/U_T/U/R/E

3. Start processing lookup “dlig2” referred by the “dlig” feature with input glyph run /F/U_T/U/R/E:
/F/U_T/U/R/E — no match
/F/U_T/U/R — no match
/F/U_T/U — no match
/F/U_T — no match
/F — no match, proceed to the next unprocessed glyph
/U_T/U/R/E — no match
/U_T/U/R — no match...

4. Etc. until all active lookups have been processed on the glyph run.

I hope you get the idea.

Best,
Adam

Eimantas Paškonis's picture

Wow. That was impressive post. Thanks!

Eimantas Paškonis's picture

So I fixed the UT problem with a help of lookups, but the war's not over.
The only thing "ignore" function seems to be ignoring is me...


Eimantas Paškonis's picture

Tried like this too.

agisaak's picture

Try the following:

feature test {
    ignore sub n' g' exclam;
    sub n' g' by n_g;
} test;

André

Eimantas Paškonis's picture

It worked! So apostrophes are the key?

agisaak's picture

Yes, apostrophes are the key here. The reason for this is that sub n g by n_g will be interpreted as a simple ligature substitution rule and will therefore be assigned to a type 4 GSUB lookup. However, your previous line ignore sub n' g' exclam specifies a contextual exception which is only appropriate for type 6 GSUB lookups. This means they end up in separate lookups in which case your ignore line won't have any effect.

By adding the apostrophes to the substitution rule, you force it to be interpreted as a contextual rule (even though no context is provided) which will allow the compiler to place these within a single type 6 lookup.

André

Eimantas Paškonis's picture

It's strange that apostrophes aren't mentioned in the manual.

Syndicate content Syndicate content