Implementing some features in a way that swaps glyphs in both directions?

Let's say you have a font that has a set of Cyrillic alternates in the “round upright cursive with extenders” aka “Bulgarian” style, and the default Cyrillic glyphs are in the “square” aka “Russian” style.  

You could implement a substitution lookup that replaces the square with the round style in the “locl” (Localized Forms) feature for the “cyrl BGR” (Bulgarian) languagesystem. Apps that support it would automatically do the replacement if the user tags the text as being set in the Bulgarian language (also for spellcheck and hyphenation purposes). 

In addition, you could implement the replacement in a stylistic set (e.g. “ss04”) for use in apps that don't support localized forms. 

But imagine that a user who users an app that supports localized forms tags the text as Bulgarian for spellcheck and hyphenation purposes, but they would prefer (for whatever reasons) to set it in the square (“Russian”) typographic style. It's not obvious how they could achieve that, because the app might not offer any UI to deactivate “locl”. 

I think the solution should be that the same “ss04” feature works both ways: 

One and the same lookup replaces the alternate forms with the default ones and the default ones with the alternates.

So if the input string had the default forms, with “ss04” applied, you get the alternates, but if it had the alternates (as a result of the previously automatically applied "locl"), you get the defaults. So effectively, you get a “swap”. There is no risk of any circular replacements if the swapping is done inside the same lookup. 

This could be of course also done in two different stylistic sets, but if your font has many stylistic sets, this might be unreasonable. So it is a question of UX. 

This technique could be used for any replacements that are performed automatically which the user might want to “undo”. What do you think? 


Comments

  • Adam TwardochAdam Twardoch Posts: 397
    edited April 20
    This might be relevant especially in cases where a certain regional preference or localized form is not supported by “virtually all” users but instead there is some “reformed” vs. “traditional” style, or where a reform was relatively recent and there may be good editorial reasons to use some earlier typographic practice, for example to preserve historical authenticity of texts written pre-reform. 

    This pertains to the recently released Minion 3. The reformed round Bulgarian style appeared in the 1960s and only recently gained popularity.

    Say that a publisher wants to set a book written in Bulgarian in the 1940s, or maybe issue a re-edition of a book that was published earlier and was set in older Minion  

    Then the publisher may wish to use InDesign’s “Bulgarian” language setting to get proper hyphenation, but may wish to preserve the “Russian style” of the Cyrillic, which may have been previously used in an earlier edition of the text. 

    If the font implements the round style using both “locl” and “ss04” (does Minion 3 do that?), then achieving it might be tricky (or is there a way to switch from Bulgarian to default already?).
  • Adam TwardochAdam Twardoch Posts: 397
    Also, this way, it doesn’t matter which Cyrillic style you put as default and which is activated via automatic localized forms. E.g. Botio Nikoltchev has done some fonts where the round style is default and the square is secondary. 

    With the method I’m describing, the stylistic set always means “switch to the other style”, regardless of which one is automatically on.
  • Adam TwardochAdam Twardoch Posts: 397
    Ps. Of course for Bulgarian Cyrillic forms, an appropriate way to switch them over to the square forms would also be the “hist” (historical forms) feature, but that's another matter entirely.
  • John HudsonJohn Hudson Posts: 1,440
    So long as the substitutions are all in the same lookup, I think this should work. If it doesn't work somewhere, then its a good test for inconsistent OTL implementation.
  • Adam TwardochAdam Twardoch Posts: 397
    edited April 20

    More generally, the reason to implement the Cyrillic replacement in both “locl” and “ssXX“ features is not just that the stylistic set needs to a fallback choice for apps that don't support localized forms. 

    The round Cyrillic forms may be the increasingly preferred localized forms for the Bulgarian language today, while many other Cyrillic language communities currently prefer the square forms — but it doesn't mean that this will always be that way.

    The Bulgarian designers from the 1960s proposed this new style as a reformed Cyrillic, not just reformed Bulgarian Cyrillic. 

    This has gained traction just in Bulgaria so far, but who knows — perhaps some communities will also like this style for another language. So offering this as a stylistic set makes sense as well, especially since fonts tend to be quite long-lived. 

  • Kent LewKent Lew Posts: 782
    Rather than just flag each of your posts as “insightful,” I’ll just come out and say it. ;-)

    Interesting line of thinking, and I think it has a lot of merit. Particularly in situations where there are fluid and evolving norms.
  • So long as the substitutions are all in the same lookup, I think this should work. If it doesn't work somewhere, then its a good test for inconsistent OTL implementation.
    To make the test even more interesting, put the substitutions within different subtables of a lookup table: once one subtable has been applied, processing of that lookup table should end.

    Of course, if the substitutions are in two separate lookup tables, then it will work for only half the scenarios you care about.
  • Adam TwardochAdam Twardoch Posts: 397
    Well, of course, I'm stupid. If I only want to let the user counteract the effects of locl by using ss04, I could implement the alternates->defaults in a lookup which is only referenced in the cyrl BGR languagesystem for ss04, while for all the other languagesystems, ss04 works have the lookup that goes defaults->alternates. Should work, shouldn't it? 
  • Kent LewKent Lew Posts: 782
    Specifying the corresponding languagesystem for the “turn-off-locl” lookup in the sset would certainly reduce the possibility of buggy interpretation like John and Peter are pondering.
  • Alex KaczunAlex Kaczun Posts: 139
    Adam—This is very interesting and useful OpenType functionality you bring up. Can you please provide all scripting examples on how to implement this in FontLab. Thank you.
  • Adam TwardochAdam Twardoch Posts: 397
    Alex, 

    will do. Very briefly: 

    My friend and colleague Botio Nikoltchev has created this guide for implementing Bulgarian "locl" alternates: 

    https://www.lettersoup.de/what-shall-be-done-for-bulgarian-cyrillic-loclbgr/

    To extend this, instead of the current recommended implementation down there as: 

    feature ss04{

     sub uni0414 by uni0414.loclBGR;

     sub uni041B by uni041B.loclBGR;

     sub uni0424 by uni0424.loclBGR;

    ...

    }ss04;


    you would do this: 

    feature ss04{

     sub uni0414 by uni0414.loclBGR;

     sub uni0414.loclBGR by uni0414;

     sub uni041B by uni041B.loclBGR;

     sub uni041B.loclBGR by uni041B;

     sub uni0424 by uni0424.loclBGR;

     sub uni0424.loclBGR by uni0424;

    ...

    }ss04;


    Of course it doesn't have to be "ss04" but can be any ssXX feature. 

    The principle is that in one and the same lookup, you have substitutions that go both ways. If you put your code right between the "feature XXXX{ ... }XXXX" keywords as shown above, without using the keyword "lookup", all the substitutions will indeed be in the same lookup, which is what you want. 

    In short, the code in "locl" goes just one way, and the code in "ssXX" goes both ways so you have twice as many substitution lines. That's the easiest way to make sure it works. 

    I'll work on some more info about this and will help extend Botio's article on his Lettersoup website. 

    Adam


  • Adam TwardochAdam Twardoch Posts: 397
    edited April 24
    A different way to implement this would be using classes. 

    # In the FEA code in the Features panel, the prefix section (in FLS5, bottom-right section, in FLVI top section), define all languagesystems that your font will support, e.g.:  


    languagesystem DFLT dflt;

    languagesystem latn dflt;

    languagesystem cyrl dflt;

    languagesystem cyrl BGR;


    # Define your "loclBGR1" OpenType class in the classes panel — or in the prefix section of the FEA code using the "loclBGR1 = [glyphname glyphname...];" syntax. 

    # That class should list all the default ("square/Russian") glyphs, i.e. the glyphs that have Unicode codepoints assigned. 

    # Define your loclBGR2 OpenType class in the classes panel — or in the prefix section of the FEA code using the "loclBGR2 = [glyphname glyphname...];" syntax. 

    # That class should list all the alternate ("round/Bulgarian") glyphs, and should have the same number of glyphs as loclBGR1, and the order of the glyphs in the class definitions should correspond. 


    # Add the feature definition for locl (in this example only for Bulgarian but your font may have other localized forms for other languages also from other scripts): 


    feature locl {

     script cyrl;

     language BGR;

     sub @loclBGR1 by @loclBGR2;

     } locl;


    # Add the feature definition for the stylistic set (here, ss04 is used as an example). Note that this feature definition makes the substitutions go both ways, which is what we're talking about here. 


    feature ss04 {

     sub @loclBGR1 by @loclBGR2;

     sub @loclBGR2 by @loclBGR1; # This very line is my "new idea", everything else has been done previously by font developers.

     } ss04;


    # I believe that this should work fine. I'll need to double-check if the two sub-class-by-class statements will correctly expand to just a list of simple substitutions in the same lookup, but I think they will. 


  • Kent LewKent Lew Posts: 782
    Or, following your second thought about this:
    feature ss04 {
    sub @loclBGR1 by @loclBGR2;
    script cyrl;
    language BGR exclude_dflt;
    sub @loclBGR2 by @loclBGR1;
    } ss04;
    (Did I get that right?)
Sign In or Register to comment.