I'm seeking advice for an unusual sequential substitution that I've been toying around with; namely, substituting the second letter in a ligature as well as the following letter, if that following letter meets certain criteria. In this particular script font there are some letter pairings that require a ligature to connect 'realistically' - things like
os, etc. What I'm trying to figure out is how I might connect multiple strings of these ligature substitutions without creating a 'hall of mirrors' in the code.
Example: est - where
es is a ligature and
st is a ligature, and both the s & t are substituted based on proximity to one another.
Obviously I can set up
sub e s' by es.lig; sub s t' by st.lig; in a feature, however the second substitution will of course be dropped if the user types
est. In my typical contextual alternates feature, I'd simply set up class substitution w/lookups (sub
@ligs @lwrcse' by
@ligs) but in this case I'm working with an uneven number of extraneous glyphs that don't line up 1:1 with what I'd like to substitute, and it's not really apples→apples anyway. i.e. There are multiple ligatures ending in 's', as one example, so class substitutions, at least in the ways I can think of, won't work.
How on earth do I code for this without writing a never-ending stream of subs? There's probably a really simple solution, short of redesigning the whole shebang, but I'm having a bit of a headscratcher moment. Doesn't help that I'm self-taught and always feeling a bit like I don't know what I don't know. I've dug down into the usual resources (TD, Opentype Cookbook, FL manuals/forums, Googlefu) but as far as I can tell, no one has asked a variant of this "special snowflake" question before. Hopefully someone out there has an answer/slap-on-the-back-of-the-head! What's the best practice here? Should I redesign the glyphs instead of attacking this via code? School me, please.
Comments
If no one else shows how, I will take a crack at it later.
Mostly we had to make decisions about hierarchy/priority and I had to do some careful ordering to properly handle the relevant combinations. It’s basically a logic puzzle.
The solution in this case required declaring a number of lookups outside of feature blocks in order to more carefully control the lookup ordering.
And yes, there was a rather long stream of subs.
I think we’d need to see examples of the sort of things you’re trying to accomplish in order to be able to advise more specifically.
Depending upon how you are drawing your connecting script, you might consider recasting your ligatures as individual component parts, especially if you think you might ever expand language coverage.
I came to this the hard way, after working on production for Jessica Hische’s Tilda, for which we had to generate a number of accented o_s, o_r, p_r, b_r, etc. ligatures.
After this experience, I encouraged the designer of an upcoming semi-connecting script to split his ligatures into individually combining parts instead. This was then much easier to automate the composition of accented variants based on the positioning of the defaults — less than an hour, rather than a day or so.
For example, rather than e_s and s_t ligatures, you might have e.calt_s, s.e_calt, s.calt_t, s.e_calt_t, and t.s_calt glyphs.
The logic puzzle becomes a bit more straightforward as a result. And it will much more straightforward to manage extended Latin development (to handle the village of Leština or footballer Néstor Álvarez, for example) later, should you so choose.
I don’t know if this kind of approach will mesh with the way you conceive your script.
It depends in part upon whether the alternates are purely functional or highly individual & stylistic and whether you can generalize the alternate connections to a manageable handful.
This. Oh, so very much this.
I remember @John Hudson, I think?, once calling these ‘ligaturings’, as in, the separate parts of a ligature. Something like ligaturelings might make more sense. Ligatidbits. Ligaments.
Contextual alternates don’t necessarily join into one fixed entity, although some would say ligatures don’t have to, but what makes dynamic ligatures distinctive, visually, is that their shape is mutable, subject to tracking/kerning.
@Jackson Cavanaugh Yes, it's a one-to-one substitution, but sometimes in sequence chains. I've played a bit with lookups and am experimenting with the ignore command (a useful first for me) but haven't figured out the logic puzzle just yet.
@Kent Lew The 'conflicting overlaps' in your BCI example is exactly what's happening in this instance, thanks for explaining that so much more clearly! Your advice/approach for the upcoming semi-connected script makes sense. I think I have the 'ligatures' set up as the individual components you're describing, just not named properly/conventionally. o_s isn't actually a connected o & s, but rather an 's' designed specifically to follow the 'o' when calt is engaged. (It should probably be named o.s_calt.) Right now they're set up like this:
The magenta/cyan indicate the shape of the individual components I've set up. Is this what you'd meant by extrapolating the ligs into individual components? Also, do you have a generic example of a lookup outside of a feature block? Is it a separate 'lookups block'?
@Nick Shinn Thanks for the Hudson reference! (And thanks @John Hudson .) God bless archived list-servs, I thought this 'dynamic ligaturing' explanation of his was illuminating and spot-on:
You should name your ligament glyphs differently. There is no one proper way, other than you should not use the kind of ligature-style base names. The base name should be the individual represented letter itself, followed by a dot-suffix. The form of the suffix is really up to you, since it is only of internal value.
The examples I gave are just one convention that I’ve worked with. Because I work on other people’s fonts (and am not always the only one), it's useful to have a system that clearly indicates the context that a glyph is intended for. But it is a little verbose, and in a more personal workflow, I’d probably simplify that for myself.
Regardless of the naming, in the specific example you showed I think the rules are pretty straightforward:
But, of course, other combinations could introduce further complexity.
However, sometimes there are multiple feature interactions where, when two features are activated at the same time, you want some rules in a downstream feature to take precedence over rules in the upstream feature — but not *all* rules (otherwise, you’d just reorder the features, right?).
If you declare a lookup block on its own, outside of a feature block, it gets compiled in the order in which it actually appears, regardless of the order of the feature block(s) that it gets registered to. (Last I checked, this is an advanced subtlety that Tal doesn’t explain in his excellent OpenType Cookbook primer.)
Here are some hastily composed, abstract examples.
In the following example, you have two features with two sets of substitutions, both for x and y.
Pretty standard. If both Stylistic Set 1 and 2 are active at the same time, then the .001 alternates will be rendered.
But suppose that, when both Sets are active, you would want the y.002 to be displayed but the x.001 to still take precedence? The solution is to declare the y.002 substitution as a lookup outside a feature block and before the ss01 feature.
In this case, the y.002 substitution is still deployed by the ss02 feature, but the lookup itself is compiled ahead of the ss01 feature lookups. So, when both features are active simultaneously, the y.002 substitution is found first and gets implemented, despite the ordering of the features (which still determines how x gets handled).
This advanced technique is usually only necessary when dealing with complex multi-feature and cross-feature interactions.
I’m not sure this will be necessary in your case, since it seems like you’re talking about everything happening within just a single liga (or calt) feature.