7 Replies Latest reply on Sep 22, 2017 5:11 PM by Thomas Phinney

    Contextual feature code samples

    Thomas Phinney Level 3
      Here are some rather unusual examples of contextual features used to achieve some special effects. I hope they inspire interesting and creative uses.

      Here are three examples of interesting effects one can achieve by using contextual alternates in OpenType. I presented and discussed the code for these at the Type Tech Forum at the ATypI Helsinki conference, Sep 14 2005. I will be showing the effects without going into detail on the code at the St Bride "Temporary Type" conference, Oct 10-12 2005.

      * * * * * *

      I came up with this code trying to help make a pseodo-random font like Beowolf work in OpenType, without using the "random" ('rand') feature.

      This example is for a case where you have eight different glyph forms for each character. While typing, the first character is shown using the first form ("default"), and each character after will use the next ascending glyph set, until you run out, when it wraps back to the beginning of the cycle.

      Repeatedly calling a single lookup allows us to keep on jumping ahead another eight characters for each lookup, making for more compact code (both written and compiled). Some assumption needs to be made about maximum line length to decide just how many times to do this.

      feature calt { # Connection or other contextual Forms
      # Latin
      lookup rotate {
      sub @default @default' by @alt1;
      sub @calt1 @default' by @alt2;
      sub @calt2 @default' by @calt3;
      sub @calt3 @default' by @calt4;
      sub @calt4 @default' by @calt5;
      sub @calt5 @default' by @calt6;
      sub @calt6 @default' by @calt7;
      } rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      lookup rotate;
      } calt;

      * * * * * * *

      This second example is similar to the first one in some ways, but I had some additional objectives that made it harder. First, I wanted to do a font where the weight of the glyphs cycles from light to black *and back*, oscillating continuously. So in terms of glyphs, it was just a full set of glyphs for each of the weights in the cycle. Lots of glyphs, but conceptually easy.

      The oscillation was a problem, because in the desired results, sometimes semibold is followed by bold, and sometimes it is followed by light, depending on whether one is on the upswing or the downswing of the cycle. I thought of two ways of solving that problem.

      One solution is two have two sets of glyphs for each of the intermediate weights. One set of semibold would be used going up in weight, and the other semibold set going down. As far as the substitutions are concerned, it starts to look like the problem above, and the substitution code is elegant, you just have even more glyphs.

      However, my second special objective was that I wanted the font to be flashy to use. In the code shown above, glyphs cycle when you add glyphs *in front* of them. But that isn't something that happens so much in normal usage. I wanted the glyphs to cycle as you type. That meant a context that stretches way out in front of the glyph being changed.

      I welcome a more elegant solution here, but between the desire to cycle and the desire to have the glyphs visually changing as you add more, I went for a pure brute force approach. This code is best read from the bottom up!

      feature calt { # Connection Forms
      # Latin
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @BLACK;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT by @LIGHT;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT by @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT by @BLACK;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by

      @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by

      @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @LIGHT;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @BLACK;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @LIGHT;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT @LIGHT by @BLACK;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT

      @LIGHT by @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by

      @LIGHT;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by

      @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @BLACK;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @LIGHT;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT @LIGHT by @SEMIBOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT @LIGHT @LIGHT by @BLACK;
      sub @LIGHT' @LIGHT @LIGHT by @BOLD;
      sub @LIGHT' @LIGHT by @SEMIBOLD;
      # sub @LIGHT' by @LIGHT; you don't actually need this line or others like it, since the glyph

      being substituted is already light. But it makes the code more readable to see it here, I think.
      } calt;

      * * * * * * *

      This last example was inspired by a talk given by Amy Papaelias at TypeCon in New York City in July, 2005. She achived the exact same effect I do here, but she used ligatures rather than contextual alternates. I claim no superiority for a contextual approach to the problem, it just seemed an interesting application of the technology.

      WARNING: This code uses (and changes) bad language. The goal is to take nasty words and censor them, either in comic book fashion with punctuation and symbols, or by changing to a different word. "F--- off" becomes "go away," "hell" becomes "heck," etc.

      feature calt {

      lookup calt1 {
      sub f u c' k by asterisk ;
      } calt1;

      sub f' u' asterisk k space y o u by g ;
      sub g asterisk' k space y o u by o ;
      sub g o k' space y o u by space ;
      sub g o space space' y o u by a ;
      sub g o space a y' o u by w ;
      sub g o space a w o' u by a ;
      sub g o space a w a u' by y ;

      sub f' u' asterisk k space o f f by g ;
      sub g asterisk' k space o f f by o ;
      sub g o k' space o f f by space ;
      sub g o space space' o f f by a ;
      sub g o space a o' f f by w ;
      sub g o space a w f' f by a ;
      sub g o space a w a f' by y ;

      sub h e l' l by c ;
      sub h e c l' by k ;

      sub b i' t c h by asterisk ;
      sub b asterisk t' c h by asterisk ;

      sub a' s s by at ;
      sub at s' s by asterisk ;
      sub at asterisk s' by dollar ;

      sub b a' s t a r d by at ;
      sub b at s' t a r d by dollar ;
      sub b at dollar t' a r d by asterisk ;
      sub b at dollar asterisk a' r d by at ;
      sub b at dollar asterisk at r' d by asterisk ;
      } calt ;
        • 1. Re: Contextual feature code samples
          Level 1
          well, i'm a bit of a novice, but i've attempted some similar things. in Cezanne Pro, there's a bit of code that looks something like this:

          feature calt{
          sub @calt1 @calt1' by @calt2;
          } calt;

          where @calt1 = [a a.salt a.salt2 a.salt3]
          and @calt2 = [a.salt a.salt2 a.salt3 a]

          there may be some problems to this logic, but it seems to work. so basically i was able to cycle through any number of alternates using just two classes.

          also, for your oscillating weights, wouldn't this work as well?:

          feature calt{
          sub @light @light' by @semibold;
          sub @light @semibold @light' by @bold;
          sub @semibold @bold @light' by @black;
          sub @bold @black @light' by @bold;
          sub @black @bold @light' by semibold;
          } calt;

          I figure that to substitute properly, you only need to know which direction the oscillation is going, so knowing the two characters preceeding should do the trick.

          now that i've posted these with out testing them. i guess i should try them in application to see if the actually work.
          • 2. Re: Contextual feature code samples
            Thomas Phinney Level 3
            I expect your code oscillating weight code will work, but it doesn't achieve my requirement of changing the *previously typed* letters. It would only make each letter typed take on the right weight. So not as flashy, but much more efficient.

            Regards,

            T
            • 3. Re: Contextual feature code samples
              Level 1
              aha! i never read closely enough. as always, thanks for sharing your code with us!
              • 4. Re: Contextual feature code samples
                Thomas Phinney Level 3
                My pleasure. :)
                • 5. Re: Contextual feature code samples
                  Level 1
                  Thomas,
                  Thanks to the link from Typophile! I am going to take a hard look at your code. Also, thanks for the many tips you and Nick Shinn gave us at TypeCon during the .calt session.

                  ChrisL
                  • 6. Re: Contextual feature code samples
                    lucaskitchen Level 1

                    Thomas, I'm a total novice on adobe script. So this is going to show my dumb..ness. I have created a font with 8 glyph alternatives. I've created a .jsx script with the code you provided. When I run the script, it gives me errors of various kinds. Am I even doing this correctly, or am I missing a really obvious step? I've created glyph sets in indesign and made sure they match what the script refers to. I don't know. Would you mind telling me if I'm missing some steps?

                    • 7. Re: Contextual feature code samples
                      Thomas Phinney Level 3

                      Hi Lucas,

                       

                      The code is OpenType feature code in the AFDKO format, which goes in a font. Many font editors including our own FontLab Studio 5 or FontLab VI use AFDKO code as source, and can compile it into a font. (So do Glyphs, RoboFont and FontForge.)

                       

                      So, you need a font editor, and you can consult its documentation on how to set up OpenType features.

                       

                      Especially (but not only) if you come from a programming background, one good intro to OpenType feature code can be found here: The OpenType Cookbook - Introduction