Multiple contextual alternates in sequence, based on proximity?
Jess McCarty
Posts: 97
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.
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.
Tagged:
0
Comments
-
You have to use the “ignore” command.
If no one else shows how, I will take a crack at it later.
0 -
I'm a little unclear. Are you using many-to-one substitutions ( sub f i by f_i ) or sequencing one-to-one substitutions ( sub f i' by i.alt )? Usually the right sequence of stacked substitutions can pull this off. Sometimes times you need to chunk out parts in separate lookups. Usually it requires a few ignore subs.0
-
I had a similar (I think), but not identical, challenge when working on features for Big Caslon Italic. There were multiple discretionary ligatures (across a couple different stylistic sets) with some triplets and many conflicting “overlaps” — e.g., n_t, t_a, a_s.
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.
0 -
Also, fwiw, and again based on a fuzzy interpretation of what you might be trying to achieve —
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.
3 -
Kent Lew said:Also, fwiw, and again based on a fuzzy interpretation of what you might be trying to achieve —
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....
....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.
This. Oh, so very much this.
0 -
Kent Lew said: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.
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.
2 -
Ligaments.
:-D
0 -
I just use the, very uninspiring, contextual alternates name.
1 -
Hudson’s term is dynamic ligaturing, IIRC.
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.1 -
That mutability is the downside to approaching ligatures as “ligaments.” However, that mutability is a vulnerability in general with a connecting script (which I am assuming Jess is talking about here; I could be wrong). So, in that case, the onus is already on the user not to muck it up, anyway.
2 -
Thanks for all the ideas & feedback everyone! Sorry I've been MIA in this thread - we've had a crazy week here on the farm and I'm just now working on the code again.
@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:The mis-analysis looks at a discrete sequence of Arabic letters and says 'This first one takes initial form, and this second one takes medial form, and this third one takes final form', and then says 'These particular medial and final letters take a special ligature shape that differs from their default connection'.
The better analysis looks at the same sequence and says 'Each of these letters has a particular appropriate form based on its neighbour(s).'
0 -
From the image you post, it does indeed look as though you’ve already conceived of things separate “ligaments,” as we’ve been suggesting. So, really you’re talking about various contextual alternates that have both before and after contexts (in technical terms: “backtrack” and “lookahead” sequences) and which connect visually to form ligatures (but aren’t composed as explicitly connected outlines in a single glyph).
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:sub o s' by s.o_;
But, of course, other combinations could introduce further complexity.
sub [s s.o_] t' by t.s_;
0 -
Regarding floating lookup blocks: Generally, lookups [groups of rules] are compiled in the same order as the feature blocks in which they are declared. And typically, this is how you want it (and why you pay attention to the order of your feature blocks).
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.feature ss01 {
Pretty standard. If both Stylistic Set 1 and 2 are active at the same time, then the .001 alternates will be rendered.
sub x by x.001;
sub y by y.001;
} ss01;
feature ss02 {
sub x by x.002;
sub y by y.002;
} ss02;
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.lookup y2 {
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).
sub y by y.002;
} y2;
feature ss01 {
sub x by x.001;
sub y by y.001;
} ss01;
feature ss02 {
sub x by x.002;
lookup y2;
} ss02;
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.
8
Categories
- All Categories
- 43 Introductions
- 3.7K Typeface Design
- 803 Font Technology
- 1K Technique and Theory
- 622 Type Business
- 444 Type Design Critiques
- 542 Type Design Software
- 30 Punchcutting
- 136 Lettering and Calligraphy
- 83 Technique and Theory
- 53 Lettering Critiques
- 485 Typography
- 303 History of Typography
- 114 Education
- 68 Resources
- 499 Announcements
- 80 Events
- 105 Job Postings
- 148 Type Releases
- 165 Miscellaneous News
- 270 About TypeDrawers
- 53 TypeDrawers Announcements
- 116 Suggestions and Bug Reports