Opentype Random Contextual Alternates
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















1.May.2006 10.17am
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
1.May.2006 10.54am
Sorry, five different character sets.
Pieter
1.May.2006 11.35am
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.
1.May.2006 11.51am
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
1.May.2006 8.31pm
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
29.Mar.2008 7.17pm
> 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;
29.Mar.2008 10.38pm
> 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;
13.May.2008 6.58pm
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?
14.May.2008 9.31am
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.
14.May.2008 11.51am
> 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”.
15.May.2008 2.56am
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?
15.May.2008 3.48am
Good question. AFAIK, marking the
@NORMAL' by @FINALsubstitution is more related with the fact that 1) whenignore 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. theignore subrule) 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.15.May.2008 4.35am
[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.
15.May.2008 5.48am
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.
15.May.2008 7.33am
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.)
15.May.2008 10.32am
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 blockWhenever 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.
15.May.2008 11.23pm
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?
16.May.2008 3.58am
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