Is it possible for a stylistic set to disable another stylistic set?

I'm trying to implement two opentype features that change the spacing of the font. ss01 makes letter-spacing wide (for small text sizes) ss02 makes it tight (for headlines). The problem is that for each of the features to work properly the other feature has to be disabled or else the incremented values of ss02 will add to the values of ss01, if the user has enabled both.
It would be great if ss02 just disabled ss01, when both are selected. Is there a way to do this?

Comments

  • Chris Lozos
    Chris Lozos Posts: 1,458
    You would have to right substitutions from one to the other in each.
  • I would think that he speaks about some GPOS rules in the features.
    And there is no way to disable a lookup from another feature.
  • Yes, I'm using rules like "pos @LC_stem_stem <16 0 32 0>;" so I can apply different values for different letter shapes grouped in classes. Thanks!
  • You cannot do this the way you wish. You want the font to be sensitive to the order that the *user* turned on two different features, and to have the "second" one take priority. There is no OpenType mechanism that would enable this.

    Users will have to turn off one feature when they turn on the other.
  • Nick Shinn
    Nick Shinn Posts: 2,216
    edited November 2014
    One way to do this is to make a duplicate set of glyphs with different sidebearings, and have that as a Stylistic Set.

    Or a separate font, which is easiest for you to make, and for users to access in application UIs.
  • Nick Shinn
    Nick Shinn Posts: 2,216
    Further to that point, I don‘t know what other foundries do, but I always make each optical sub-family (e.g. “-Display”) a separate menu item
  • Thank you Thomas, Nick

    I think I found a way to make one stylistic set take priority over another.

    I made a lookup group, separated from the features, with all the GPOS rules needed to tighten letter spacing.
    Then the SS01, the feature that tightens letter-spacing, just calls that lookup.
    SS02 is made of two parts, first it calls the same lookup to tighten the space, then it adds rules that open the spacing on top of that lookup. The lookup seems to run only once, even if it's called by more then one feature, so in order for SS02 to work it is the same thing whether SS01 is activated or not.

    Anyway, to have these features may be dangerous, because if the user enables for instance SS01 to tighten the letters and then opens tracking manually, the letter-space will be harmed.
    So I'm not sure if I will include this feature. If I do I'll only keep the one for display, so this danger is not so big. Plus a feature for wide letter-sapce is not very relevant, because it wouldn't be very different from the user increasing tracking.

    "…I always make each optical sub-family (e.g. “-Display”) a separate menu item"
    Nick, because this is a grotesque sans with even counters, very low contrast etc, it turns into a display font easily, just by tightening letters-pacing by categories of letters (to make sure for instance, that consecutive round edges don't touch). So I thought a feature for this would be nice. My worries is that users don't get it.
  • Kent Lew
    Kent Lew Posts: 944
    Rui — I had thought of suggesting what you did, with the second sset subsuming the first lookup and then compensating accordingly.

    But when I tried it, the affect was cumulative when both ssets were activated — the first lookup was applied twice and compounded. So, I didn’t post the suggestion.

    Now, I did not declare my test lookup outside the first sset, instead just declaring a named lookup within the first sset, which was then also called within the second sset. I’m not convinced this should have made a difference. But perhaps it did.

    I did my testing in RoboFont with Feature Preview. I did not compile the test. So different applications may in fact implement differently.
  • Kent, I only tested the compiled font in IDCS5 and the features weren't commulative, it worked well. Perhaps declaring the lookup outside the feature may work, but I'm not secure this works everywhere either.
  • It's extra coding and a crock of a kludge, but why not just explicitly unsub glyphs? If ss01 subs /j by /j.alt and ss02 subs /q by /q.alt but /q.alt and /j.alt look terrible together (maybe not the best example, but whatevs), why not have ss02 sub /j.alt by /j? The OT compiler doesn't complain, after all. I guess it would depend on the order in which each ss is applied (and I'm too tired to bother testing right now); I think they're applied in order (ss01, then ss02, and so forth), or maybe in the order declared in the feature file.
  • Kent Lew
    Kent Lew Posts: 944
    Michael — Rui is using GPOS rules, not GSUB. They work differently. “Unsub”-ing is not an option in this case.
  • Kent -- Oops, missed that. (Tunnel vision, working with alt glyphs, too little sleep, yadda yadda.) My apologies to all.
  • Rui,

    one thing that may disappoint you is that in apps using the Microsoft OpenType Layout engines (Microsoft Office, CorelDraw X6 and others), GPOS-based ssXX features don't work. The technology of OpenType Layout is such that first, all GSUB features are applied and then, all GPOS features are applied. It seems that Microsoft chose to run certain features only in the GSUB step, and others in the GPOS step. I think this is not a good choice, but currently, we're stuck with it. Other OpenType Layout engines (HarfBuzz, Adobe/ICU and CoreText) work fine. I made a set of free test fonts which use GPOS-based ssXX features, available from:
    http://sites.twardoch.com/typography/font-tech/gpos-stylistic-sets

    The way that OpenType Layout works is that for user-selectable features such as Stylistic sets (but also others such as dlig, liga, smcp etc.), the engine checks which features need to be applied, then collects the list of lookups associated with all the enabled features (first all GSUB lookups, then all GPOS lookups), and then just goes through the lookups in the font-defined lookup order one by one, and applies them to the current text run.

    At the time that the lookups are applied, the engine no longer cares which features they were assigned to. It just executes the lookups.

    Also, within one lookup, the lookup itself does not "know" which lookups have been applied before it, or which lookups will be applied later. So if your ss01 feature has the GPOS lookup #12 assigned, and your ss02 feature has the GPOS lookup #14 assigned, the GPOS lookup #14 has no way of "knowing" if lookup #12 has been executed before it or not. Similarly, GPOS lookups have no real method to "measure" which distance already exists between certain glyphs (there is no "postition context" mechanism of any kind).

    This is a weakness of the OpenType Layout model, and I cannot think of a method to work around it, i.e. to allow you to achieve what you're trying to achieve.

    Perhaps someone could think of a super-clever method to do it, but AFAIK, the only way to introduce a fake "state-like" mechanism is through substituting glyphs. You could make a 2nd set of completely identical glyphs (even with the same sidebearings) which you'd swap in the GSUB ss01 lookup, and in the GSUB ss02 lookup, you'd swap the clones back to the originals. Then, in the GPOS ss01 lookup you'd add the space to the clones, and in a GPOS ss02 lookup you'd remove the space from the originals.

    If you plan to add or remove the same amount of space everywhere, another method might be to make a "posspace" blank glyph with positive width, and a "negspace" glyph with a negative width, and then have GSUB ss01 do something like:

    sub A by A posspace;
    sub B by B posspace;

    etc. — analogical for ss02 and negspace.

    This will, however, introduce complications with things like kerning because after running ss01, your /A/V/E input string will actually be /A/posspace/V/posspace/E/posspace so you'd have to write a contextual kerning lookup for your kern feature.
  • PS. It does not matter at all whether in FEA you declare your lookup inside or outside feature declarations. What matters is the order of lookups after FEA has been compiled. In final OTL tables, lookups are stored as one ordered list for GSUB and one list for GPOS, and features only refer to the lookups.
  • Kent Lew
    Kent Lew Posts: 944
    the engine checks which features need to be applied, then collects the list of lookups associated with all the enabled features (first all GSUB lookups, then all GPOS lookups), and then just goes through the lookups in the font-defined lookup order one by one, and applies them to the current text run.

    Adam — This is basically what I always understood. The question then is, which of the two reported behaviors is “correct”?

    If a feature ss01 references GPOS lookup #12, and feature ss02 references both GPOS lookup #12 and GPOS lookup #14, then when both ss01 and ss02 are active, should the GPOS lookup #12 positioning be applied twice or just once?

    With GSUB, after a lookup is applied presumably the target conditions have changed, so there is, in essence, no such thing as a second application. But it would appear to be different with GPOS, or at least in some implementations.

    I suppose the difference is whether, when collecting the list of lookups associated with all the enabled features an engine will allow multiple listing of the same GPOS lookup[s].

    Does anyone have a sense of which engines do and which do not? It would seem from Rui’s test that Adobe does not, and from my test that whatever engine Tal used in Area 51 does.
  • I think lookup 12 should be called twice. Multiple calls of the same lookup have productive use such as in Tom Phinney's pseudorandomization example: https://forums.adobe.com/thread/395648
  • Kent Lew
    Kent Lew Posts: 944
    I see your point. And therefore, the Rui’s “solution” should not have worked in InDesign. But there you go.
  • rui_abreu
    rui_abreu Posts: 23
    edited November 2014
    Hi Adam, Kent. Thank for coming in with this info.

    Since I first posted this question, I basically quit implementing this, mainly because of the user experience. I wanted to alter the spacing between the letters, but not the same amount for every letter. I basically divided letters by classes (straight, round, angles, slanting) and then apply different values with GPOS rules. That means that if, for instance, the user applied the feature that tightens letter spacing, and then change his mind or forget it and opened tracking manually, the spacing would be all wrong. To avoid that kind of stuff I droped it.

    Anyway, at one point I thought I had it working as I wanted because I had ss01 calling a lookup that removed space, and the ss02 called the same lookup plus a new lookup that added space, compensating for the previous lookup. From what I saw in InDesign, when two features called the same lookup, that lookup ran only once, which meant that, ss02 could just run over ss01 without accumulating values. Most probably it won't work like this everywhere.

    I also tried a second approach: instead of having two ssXX (one that removes space and another that adds space) I decided to keep only one: the one that removes space (the style for headlines). To make it clearer for the user, and make the feature also available on Photoshop, I decided to implement that as titl feature and not as a ssXX feature. The problem is that it didn't work on Photoshop. The titl feature with GPOS and GSUB rules worked well in Indesign, but in Photoshop the GPOS rules didn't work. So I just dropped the Idea.

    One further thing that may be related to what Adam explained is that GPOS rules in the cpsp feature don't seem to work in Photoshop either.
  • Kent Lew
    Kent Lew Posts: 944
    I think GPOS support in Adobe apps is still a bit spotty. In InDesign, for example, many GPOS features will only work if one of the World-Ready composers is active (which generally they are not, by default). I do not know if the World-Ready composer has been implemented in Photoshop or not (I have only up to CS5.1, which does not have it).

    Yeah, your concept was probably always a bit impractical, but interesting nonetheless; and the ensuing investigation was valuable. I always enjoy increasing my understanding of what happens under the hood.