Reversing glyph sequence

I'd like to be able to do things like:
sub A A B B by B B A A;
sub A B C D E F by F E D C B A;<br>
I don't see how this could be achieved reading the spec, and FontTools errors out with an "Direct substitution of multiple glyphs by multiple glyphs is not supported" if I try the above, but I was wondering if there's a trick to still accomplish this.


  • It is not possible in a single lookup, but you can accomplish it with two lookups.

    First a ligature substitution:
    <code>A_A_B_B;sub A A B B by 
    Then a multiple substitution:
    <code><code>A_A_B_B by B B A A;sub 

  • OpenType does not support multiple to multiple glyph substitution. You can use hacks like the above, but be aware that it will mess with the glyph to character. In the example above you might not be able to select the individual A and B glyphs anymore.
  • K PeaseK Pease Posts: 104
    edited October 2019
    Preservation of the plaintext is central to OpenType. If you have just a small list of words to reverse, the best thing would be to make a ligature substitution and stop.
    If you want a generalized process, you'll need a big matrix of glyphs with classes for the rows (what the letter really is) and columns (what it looks like). For instance A.Z would be an alternate A whose outline looks like a Z. Then you would need a block of code for each string length you want to be able to do, substituting each character one by one.
    Here's what the code would look like for four-character strings only, and if the only characters involved were A, B, C. For better comprehension I'm showing most of the classes as bracketed groups and leaving in the members that would be unchanged, but for a larger set you would definitely use defined classes.
    sub [A B C]' @any @any A by [A B.A C.A];<br>sub [A B C]' @any @any B by [A.B B C.B];<br>sub [A B C]' @any @any C by [A.C B.C C];<br>sub @any [A B C]' A @any by [A B.A C.A];<br>sub @any [A B C]' B @any by [A.B B C.B];<br>sub @any [A B C]' C @any by [A.C B.C C];<br>sub @any [A A.B A.C] [A B C]' @any by [A B.A C.A];<br>sub @any [B.A B B.C] [A B C]' @any by [A.B B C.B];<br>sub @any [C.A C.B C] [A B C]' @any by [A.C B.C C];<br>sub [A A.B A.C] @any @any [A B C]' by [A B.A C.A];<br>sub [B.A B B.C] @any @any [A B C]' by [A.B B C.B];<br>sub [C.A C.B C] @any @any [A B C]' by [A.C B.C C];
    Every character more in the pool will multiply the number of lines of code, and there's no getting around it, lacking some kind of two-dimensional ordered meta-class.
    If you really want to break the plaintext, you could substitute it afterward, but the alternate glyphs are necessary to keep track during the second half of the process, so you may as well not.

  • @Erwin Denissen @Khaled Hosny Thanks, I gave that a shot. Swapping via an intermediate glyph seems to do the trick, but indeed the glyphs are then not processed by any subsequent rules. Which in my case is a dealbreaker, but good to know this workaround exists.

    @K Pease Thanks for the thorough answer. I was afraid this would result in an explosion of rules (and my brain). Appreciate the example code!
  • OpenType features should continue to be processed.

    <div>script latn {<br>&nbsp; feature StandardLigatures1;<br>}<br><br>feature StandardLigatures1 liga {<br>&nbsp; lookup Ligature1;<br>&nbsp; lookup MultipleSubstitution1;<br>&nbsp; lookup Ligature2;<br>}<br><br>lookup Ligature1 {<br>&nbsp; sub a b c -> a_b_c;<br>}<br><br>lookup MultipleSubstitution1 {<br>&nbsp; sub a_b_c -> c b a;<br>}<br><br>lookup Ligature2 {<br>&nbsp; sub b a d -> checkmark;<br>}<br></div><div></div>

  • @Erwin Denissen You are correct, I tested your approach in a regular font and features continue to be applied on the replaced glyphs. So something else in my particular setup is ignoring the advance widths of the replaced glyphs ("c b a" in your example). This might be caused the by the color font format I'm using — will investigate.

    Thanks for posting the example!
  • In my example
    (A) advance width = 771
    (B) advance width = 637


    lookup AABB {
    pos A' <2045 0 0 2045> A' <503 0 0 503> B' <-905 0 0 -905> B' <-2179 0 0 -2179>;
    } AABB;


  • Here's another way:

    feature test {

    lookup AB {
    sub A by B;
    } AB;

    lookup BA {
    sub B by A;
    } BA;

    } test;

    #These lookups can be placed in unused feature like in example above or outside a feature block.

    feature ss01 {

    lookup AABB {
    sub A' lookup AB A' lookup AB B' lookup BA B' lookup BA; 
    } AABB;

    } ss01;

  • @Konstantin Golovchenko Thanks for these examples. I like the faux order flipping by using GPOS!
  • The GPOS trick is fun, but ... I would worry about it in actual use, as I would expect it to interact rather badly with kerning, tracking, and justification.
Sign In or Register to comment.