Opentype Random Contextual Alternates

scruggsdesign's picture

Ok, Opentype wizes…
I’ve been using Thomas Phinney's Opentype random code he posted in the Adobe user to user forums
http://www.adobeforums.com/cgi-bin/webx?50@755.561rfSM0Gas.1@.3bbc5ea4
and it works great! I’m working on a typeface that has one alternate for each Uppercase charicter and three alternates for each lowercase charicter making 2 different glyph forms for the Caps and 4 different glyph forms for the lowercase. My question is what is the logic behind repeating "lookup rotate;" 13 times? Does it relate to having eight different glyph forms?
my code looks like this:
feature calt {
lookup UCrotate {
sub @default_UC @default_UC' by @UC_calt1;
} UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;
lookup UCrotate;

lookup lcrotate {
sub @default_lc @default_lc' by @lc_calt1;
sub @lc_calt1 @default_lc' by @lc_calt2;
sub @lc_calt2 @default_lc' by @lc_calt3;
sub @lc_calt3 @default_lc' by @lc_calt4;
} lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
lookup lcrotate;
} calt;

Thanks!

Josh

Pieter van Rosmalen's picture

Hello Josh,

This routine also works (four different characters):

feature salt {
lookup rotate {
sub @set_1 @set_1' by @set_2;
sub @set_2 @set_1' by @set_3;
sub @set_3 @set_1' by @set_4;
sub @set_4 @set_1' by @set_5;
sub @set_5 @set_1' by @set_1;
} rotate;
lookup rotate;
} salt;

Pieter

Pieter van Rosmalen's picture

Sorry, five different character sets.

Pieter

scruggsdesign's picture

Thanks Pieter,
that works fine and its a more compact code.
what does calling the "lookup rotate;" thing do?

also, is there a way to incorporate "space" into the sequence? Whenever a space is used it seems to reset the sequence.
for example:
typing "dog dog dog"
gives me:
d1 o2 g3 space d1 o2 g3 space d1 o2 g3 space.
I would like it to work like this:
d1 o2 g3 space4 d5 o1 g2 space3 d4 o5 g1

I tried inserting the space character into all of the sets but that didn't work.

scruggsdesign's picture

I figured out the space problem. I had to create glyphs for space.calt1 space.calt2 space.calt3 and space.calt4 and place them in their respective sets.

Josh

Thomas Phinney's picture

Yes, that's exactly it.

IIRC, the reason I used "lookup rotate" was just to save space, both in writing out the code and in the compiled font. The same thing can be done with more lengthy code....

T

Miguel Sousa's picture

> what does calling the “lookup rotate;” thing do?

In terms of code, what it does is "makes a call" to the lookup defined above, which means that each 'sub' is "ran" again. Again because this part of the code

lookup rotate {
sub @set_1 @set_1' by @set_2;
sub @set_2 @set_1' by @set_3;
sub @set_3 @set_1' by @set_4;
sub @set_4 @set_1' by @set_5;
sub @set_5 @set_1' by @set_1;
} rotate;

not only defines the lookup, but also "runs" each 'sub' included in it.

==========================================

In terms of result, the line “lookup rotate;” does nothing, really. If you analyze step by step what the code does, here's how it goes:

A. Let's consider a word with 10 letters as the input. Each of these letters have their default glyph, represented by "1", and four alternates glyphs, represented by "2", "3", "4" and "5". In the beginning, all those letters are taking their default glyph shape, since no feature is applied. So, our 10-letter word looks like this, 1111111111 (where "1" represents the default glyph of each letter).

B. After going through the first 'sub' (sub @set_1 @set_1' by @set_2;), our word now looks like this, 1212121212, where "2" represents the first alternate glyph. What the code does here is replace the second default glyph (in a pair) by the alternate glyph.

C. The second 'sub' changes the displayed word to 1231231231.

D. The third 'sub' changes it to 1234123412.

E. The fourth 'sub' changes it to 1234512345.

F. And the fifth and last 'sub' (sub @set_5 @set_1' by @set_1;) does nothing, because it's replacing the default glyph (a.k.a. "1") by itself.

G. Then the call to the 'rotate' lookup does nothing as well, because at this stage our word doesn't have any two default glyphs next to each other (i.e. "11"). Neither the first alternate ("2") appears followed by the default glyph ("1"). And so on...

Bottom line, Pieter's code can be further reduced to just,

feature salt {
sub @set_1 @set_1' by @set_2;
sub @set_2 @set_1' by @set_3;
sub @set_3 @set_1' by @set_4;
sub @set_4 @set_1' by @set_5;
} salt;

Miguel Sousa's picture

> I figured out the space problem. I had to create glyphs for space.calt1 space.calt2 space.calt3 and space.calt4 and place them in their respective sets.

Alternatively, you could have added a few more substitutions including the 'space' glyph in the context, like this:

feature calt {
sub @set_1 @set_1' by @set_2;
sub @set_2 @set_1' by @set_3;
sub @set_3 @set_1' by @set_4;
sub @set_4 @set_1' by @set_5;

sub @set_1 space @set_1' by @set_2;
sub @set_2 space @set_1' by @set_3;
sub @set_3 space @set_1' by @set_4;
sub @set_4 space @set_1' by @set_5;
} calt;

shockomotive's picture

I get the code, and thank you very, VERY much for it, Miguel, but I have a question:
is it relevant whether you pack the substitutions into the contextual alternates or the stylistic alternates feature?

The more semantically correct would be salt, I believe, because the characters are not replaced in relation to their neighbours but simply by their order of appearance.

Would it have any consequences whatsoever if you used calt instead?

Jens Kutilek's picture

salt can be accessed in Illustrator but not in InDesign, IIRC. I think what you're doing are not stylistic alternates. That would be more something like switching between a one- and two-storey "a" etc. Slight variations of the same drawing are not stylistically different in my opinion.

I'd say, use calt. Applications should, following the feature specification, turn this feature on by default, so that's what you'd want.

Jens.

Miguel Sousa's picture

> is it relevant whether you pack the substitutions into the contextual alternates or the stylistic alternates feature?

Yes. The Stylistic Alternates (salt) feature should only contain one-to-one substitutions (GSUB lookup type 1) or one-from-many substitutions (GSUB lookup type 3), whereas the Contextual Alternates (calt) feature is meant to only contain contextual substitutions (GSUB lookup type 6). In other words, don't put contextual substitutions in 'salt', and one-to-one or one-from-many substitutions in 'calt'. Applications expect that the features in the font are constructed according to the specs, so you might not get the desired behavior if you don't follow them. (Disclaimer: some feature descriptions are not as clear as they needed to be, so some OT layout implementations might slightly differ.)

> The more semantically correct would be salt, I believe,

I disagree, but I think I understand why you say so. Think of 'calt' as the contextual version of 'salt' (which is similar to the relationship between swsh and cswh). If you apply 'salt' to a whole block of text, you might see wired things like this,

On the other hand, if you instead apply 'calt' to the same block of text, you might get this result instead,

(Of course, this assumes that the font has the right substitutions in the right features and the application has support for them and applies them correctly.)

To get the 'calt' result "automatically", the feature and the substitutions have to rely on the context. In the case above, all glyphs with a finial variant are being replaced whenever they are positioned at the end of the word (this is the context), but not when they appear elsewhere.

> because the characters are not replaced in relation to their neighbours but simply by their order of appearance.

And how do you determine their order of appearance? Don't you have to analyze what's around, and therefore "look" at the context?

I should add that the result displayed in the 'calt' picture can also be achieved by using the Terminal Forms (fina) feature. In this case the code of the feature is much simpler (see below), because the task of deciding when to apply the glyph substitution is left to the application. Unfortunately, very few applications support the 'fina' feature. (InDesign CS3 does)

@ALL_LETTERS = [a-z A-Z];
@FINAL = [a.fina t.fina];
@NORMAL = [a t];

feature fina {
sub @NORMAL by @FINAL;
} fina;

feature calt {
ignore sub @NORMAL' [@ALL_LETTERS @FINAL];
sub @NORMAL' by @FINAL;
} calt;

As you see, to get the effect of the 'fina' feature via the 'calt' feature, the code get's more complex. This is because using the 'calt' feature we have to put the "intelligence" in the font, whereas using the 'fina' feature we can rely on the application's "intelligence".

pvanderlaan's picture

Miguel,

In the calt feature you write: sub @NORMAL' by @FINAL; a contextual substitution without any context (which appears illogical to me). Is this only to comply to the rule that *all* substitutions in the calt feature must be contextual?

Miguel Sousa's picture

Good question. AFAIK, marking the @NORMAL' by @FINAL substitution is more related with the fact that 1) when ignore sub(stitute) is used, at least one glyph or glyph class must be marked (When no glyphs are marked, then only the first glyph or glyph class is taken to be marked). Additionaly, in order for the exception to the chaining substitution rule (i.e. the ignore sub rule) to affect the rules that follow it, 2) they all must be of the same type so that they are put in the same lookup (A lookup is a group of rules of the same type). Since the exception will of type GSUB LookupType 6, the following rules need to be expressed in the same type, and that's achieved by marking the sequence.

k.l.'s picture

[While I was writing, Miguel has already answered the question ...]

The ignore statement does a nice trick when writing GSUB or GPOS tables. I'll try a simplified (so not entirely correct) description:

In the compiled font's GSUB table, the "main" lookup for this calt feature will not only contain a subtable listing all the substitutions (here "sub @NORMAL by @FINAL;"), but also an additional subtable ahead of it. This additional subtable will list affected glyphs and their contexts (here "@NORMAL' [@ALL_LETTERS @FINAL]") but no instructions about what to do with them. It does nothing, actually -- the sole purpose of this subtable is to "catch" all contexts listed in the ignore statement.
Layout engines follow the rule that if a glyph or glyph sequence has been consumed by a subtable, then all following subtables will be skipped for this glyph or glyph sequence. So the trick works like this: If the ignore context is matched for a glyph: substitutions for this glyph, as defined in the following subtable, will not be executed (they will be ignored). If the ignore context is not matched: substitutions for this glyph, defined in the following subtable, will be executed.

(And to make sure that the real substitutions will go into the same lookup as the mere "context catchers" -- although into different subtables -- they must be of the same lookup type. Which is achieved by defining "sub @NORMAL' by @FINAL;" in pseudo-contextual form, as Miguel explained.)

VOLT's EXCEPT does about the same thing, AFAIK.

pvanderlaan's picture

Thanks both for the explanations! I still have to make some tests myself to fully comprehend the effects of the "ignore" command but your reactions are very helpful indeed.

k.l.'s picture

At best, generate a font with a single feature with one substitution and tiny ignore context, and inspect the GSUB table with spot and ttx. (In this case, ttx's indentations are more helpful to get the structure.)

Miguel Sousa's picture

BTW, if you remove the quote marks from the substitutions and then explicitly make them part of the same lookup, like this,

feature calt {
lookup TEST {
ignore sub @NORMAL [@ALL_LETTERS @FINAL];
sub @NORMAL by @FINAL;
} TEST;
} calt;

when you try to compile the code you'll get the following error,
[FATAL] <MyFont-Regular> Lookup type different from previous rules in this lookup block

Whenever I need to put together some code that involves using the 'ignore' statement, I tend to write the simpler subs first, and then figure out what the ignore sub needs to be in order to get the desired behavior.

Nick Shinn's picture

In the Phinney method, with four versions of each character, there will be repetitions every 5th and 9th character, &c.
For instance, in the phrase "this is the way the men think", all the "th"s will be identical.

So, replace a (Sousa-termed) sequence of 1234 1234 1234 with:

1234 1324 3142

In this sequence, there are no repeated combinations.
Here's how:

feature calt {

lookup calt_one {
sub @set_1 @set_1' by @set_2;
sub @set_2 @set_1' by @set_3;
sub @set_3 @set_1' by @set_4;
} calt_one;

lookup calt_two {
sub @set_4 @set_1 @set_2' by @set_3;
sub @set_3 @set_3' by @set_2;
sub @set_2 @set_4 @set_1' by @set_3;
sub @set_4 @set_3 @set_2' by @set_1;
sub @set_3 @set_1 @set_3' by @set_4;
sub @set_4 @set_4' by @set_2;
} calt_two;

(A longer "mixed up sets" sequence would be required to fully deal with "this is the way the men think".)

In removing repeated combinations, the trade-off is a reduction of some of the distances between repetitions (the smallest here being "...212..." which occurs at the end of the sequence).

So it may be better to have at least five sets, and a sequence such as:

12345 21453 24135

Within this sequence of fifteen characters, there is only one repeated adjacent combination, and repeated sets are separated by at least three characters.

**

There is a way to cycle through the variants of a character each time it appears:

feature calt {

sub A A' by A.alt;
sub A @NoA A' by A.alt;
sub A @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt;
sub A @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt;
sub A.alt A' by A.alt2;
sub A.alt @NoA A' by A.alt2;
sub A.alt @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt2;
sub A.alt @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt2;
sub A.alt2 A' by A.alt4;
sub A.alt2 @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt3;
sub A.alt2 @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA @NoA A' by A.alt3;
} calt;

...and so on, where @NoA is all the letters, punctuation, etc. that may occur between instances of the character "A", but not A.

This is rather long-winded, as it has to be done separately for every character one wishes to cycle, so one could end up with hundreds of classes of hundreds of glyphs each. (In practice, this may only be necessary for the most noticeably different of the most frequent letters, especially "t".)

I wonder if so much coding would appreciably slow down rendering?

solfeggio's picture

In practice, this may only be necessary for the most noticeably different of the most frequent letters, especially “t”.

Just a thought, Nick: For longer passages of text (i.e., temporarily setting aside your "this is the way the men think" example), wouldn't cycling sets of vowels be a better method of changing the most frequently occurring letters?

Fine, it mightn't be ideal with transliterated Serbian ;)

Still, curiosity heightens: how could one order such cycling vowel lookups and the larger calt sets (viz., the numbered sets you've used above) to work neatly together? Or is the assumption that it could be done in itself a leap too large?

Regards,
Ernie

k.l.'s picture

Nice to see this old discussion again. As a belated reaction:

Nick: I wonder if so much coding would appreciably slow down rendering?

It does significantly in IDCS2 on a 10.3.9 driven G4 PowerBook. The fonts tested only contain around 200-300 glyphs which include 2-3 variant glyph, and allow for about 12 glyphs inbetween any two recurring "same" glyphs. Not tested in IDCS4 on a newer machine.

blank's picture

Re performance; it depends on the app. Indesign and Photoshop handle this kind of thing just fine on my Macbook Pro, but Illustrator rendering can lag half-a-second after a key is pressed.

twardoch's picture

In 2005, importing a 50-page text file into InDesign (CS or CS2, I don't remember) that used a "simple" font took some 15 seconds while importing the same file that used Zapfino Extra Pro (which had a "calt" feature consisting of 60 or so lookups) took some 5 minutes. But once the text was rendered, there was no visible delay.

However, in a long document in Notepad on Windows XP at that time, Zapfino Extra Pro caused significant delay on every page scroll — because Notepad was re-running the OT code on every screen refresh while InDesign apparently did some glyph caching.

Michael Jarboe's picture

Nice thread… I look forward to using the 'random' feature one day… endless possibilities…

neil summerour's picture

Hi Everyone....

I stumbled on this thread and it has been immensely helpful...but I have a curveball question from this...

What if you want to keep same letter pairs together because the variant options of each letter, when put side by side, are too distracting...(one and two story a's aa or a standard and binocular g's gg)

is there a way to exclude or revert specific double letter substitutions?

neil summerour's picture

nm. solved.

Thomas Phinney's picture

For others who might wonder the same thing: I expect you'd just process the problem combo afterwards in a separate lookup. So for instance if you wanted whichever version of "g" came first to take precedence you could have a lookup that did:

sub g g.alt' by g
sub g.alt g' by g.alt

Cheers,

T

vn's picture

I’ve just read this thread and it helped me a lot to improve my own code for the ‘random alternate’ feature with ‘calt’. I’ve combined the random thing with the last-letter-is-different-thing (‘fina’) in one simple calt-feature code and it works pretty well...
unfortunately not for the client I’m doing this for. He works with Photoshop CS2 on Mac and the ‘Contextual Alternates’ seems not to be active. (We also tried a different font calt-feature that didn’t work.) I guess this is a CS2 issue and I wonder if any of you has come over this problem before and knows a way to handle it.

Regrads, Viktor

Grrrben's picture

Thanks for the other suggestion, Nick. That alternative (@NoA) is just what I need at the moment. However, it seems like the code ends up too long:

[FATAL] (MyFont) Chain contextual lookup subtable in GSUB feature 'calt' causes offset overflow.

Any thoughts, anyone? Any trick to make it work? Many, many thanks in advance.

agisaak's picture

Try explicitly declaring your lookups with the useExtension directive.

http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.htm...

André

Grrrben's picture

Thank you, André!

Tried, but unfortunately it didn't work out. No error message but it just didn't compile at all. To make it work I had to trim the waterfalls' length...

Kind regards,
Gerben

Duncan MacLeod's picture

Hi folks,

I have been using Nick Shinn's code above with no problems at all, so first let me say thank you to all of you here for all of your hard work on this.

I would, however like to expand the code as nick suggested to the five set
"12345 21453 24135" sequence, and was hoping someone could check my work. (Or, alternatively, help me set up a six set sequence, if one would be so inclined - but that isn't critical.)

Here is the code I have got for a five set sequence; please point out any errors - I'm not very good with number puzzles:

feature calt {

lookup calt_one {
sub @set_1 @set_1' by @set_2;
sub @set_2 @set_1' by @set_3;
sub @set_3 @set_1' by @set_4;
sub @set_4 @set_1' by @set_5;
} calt_one;

lookup calt_two {
sub @set_5 @set_1 by @set_2;
sub @set_2 @set_2 by @set_1;
sub @set_2 @set_1 @set_3' by @set_4;
sub @set_4 @set_4' by @set_5;
sub @set_5 @set_5' by @set_3;
sub @set_5 @set_3 @set_1' by @set_2;
sub @set_3 @set_2 @set_2' by @set_4;
sub @set_2 @set_4 @set_3' by @set_1;
sub @set_4 @set_1 @set_4' by @set_3;
} calt_two;
} calt;

I'm particularly worried about a "2-2" sequence when the code gets to the beginning of the third 'word,' where the code calls for sets "3-2-2 by 4".

Thanks for any help you can give.

Thomas Phinney's picture

In the second lookup, are the first couple of substitutions missing their prime mark?

I can't tell if the code is okay without knowing what you are trying to accomplish. Can you explain how you want the alternates to cycle? Is it just 123451234512345...?

Duncan MacLeod's picture

Oops, you're right about the prime marks - let's consider that a minor typo. ;) I'll make sure that is fixed for final use.

Above, Nick suggested the pattern of "12345 21453 24135" and I am just trying to figure out if I have the 'replacement cycle' correct.

Let me re-state the process of replacement for clarity, as I see it happening - please let me know if I'm getting off the rails anywhere:

The glyphs are split into sets; the primary set (@set_1 [A-Z, a-z, 0-9, etc.]), and four other 'alternate' sets of the same glyphs you wish to be swapped out; sets @set_2 through @set_5.

The glyphs start out as: 11111 11111 11111

After the first lookup, the glyphs are then: 12345 12345 12345

Then, the second lookup does this sequence, line by line;
12345 12345 12345 - start
12345 22345 12345 - lookup 2 line 1
12345 21345 12345 - lookup 2 line 2
12345 21445 12345 - lookup 2 line 3
12345 21455 12345 - lookup 2 line 4
12345 21453 12345 - lookup 2 line 5
12345 21453 22345 - lookup 2 line 6
12345 21453 24345 - lookup 2 line 7
12345 21453 24145 - lookup 2 line 8
12345 21453 24135 - lookup 2 line 9

Then the sequence should look like: 12345 21453 24135, as Nick suggested.

I just wanted to make sure I have the theory correct, and the cycle will act as predicted. Thus, if expanded to six sets, I know what the sequence should do. Sorry for the long-winded explanation, and I hope I was clear enough. Thanks for any help.

Nick Shinn's picture

I didn’t have a formula for calculating the succession of sequences, just trial and error.

Duncan MacLeod's picture

Thank you for that sir, but perhaps I wasn't clear enough in what I was asking; I just wanted someone to check my code, to make sure it would work properly.

I never expected two of the pioneers of this subject to answer. I'm humbled.

Anyway, I finally had a chance to check out the code in FLS5, and discovered that it stopped subbing glyphs in the middle of the second set (where I have subsequently placed the third lookup break), and in order for it to complete all of the substitutions, I needed to split the code into the three lookups shown below.

Here is the code:

feature calt { # Contextual Alternates
# Latin
lookup calt_one {
sub @set_1 @set_1' by @set_2;
sub @set_2 @set_1' by @set_3;
sub @set_3 @set_1' by @set_4;
sub @set_4 @set_1' by @set_5;
} calt_one;

lookup calt_two {
sub @set_5 @set_1' by @set_2;
sub @set_2 @set_2' by @set_1;
sub @set_2 @set_1 @set_3' by @set_4;
sub @set_4 @set_4' by @set_5;
sub @set_5 @set_5' by @set_3;
} calt_two;

lookup calt_three {
sub @set_5 @set_3 @set_1' by @set_2;
sub @set_3 @set_2 @set_2' by @set_4;
sub @set_2 @set_4 @set_3' by @set_1;
sub @set_4 @set_1 @set_4' by @set_3;
} calt_three;
} calt;

Here are my results (spaces are demarcated with a bar above the number):

[EDIT]: I should clarify the image below; I created a typeface with the letter glyphs replaced with numerals - "1" for set_1 letters, "2" for set_2 letters, etc., and again, with spaces marked with bar above. Then, when the calt code was run in FLS5, you see the following:

It seems to work perfectly, but does anyone have any thoughts on why the code needed to be separated into three blocks? Any other thoughts?

Syndicate content Syndicate content