[Tex/LaTex] Can one (more or less) automatically suppress ligatures for certain words

contextligaturesluatextypography

This question led to a new package:
selnolig

One of the major attractions — for me at least — of typesetting my papers in (La)TeX is its automated and fully transparent use of typographic ligatures. However, as Knuth pointed out in the TeXbook, it is sometimes necessary to disable or suppress ligatures in order to either improve a word's legibility (Knuth uses the word shelfful as an example) or so as not to inadvertently change its meaning, which is especially important in some non-English European languages, such as German, which have lots of compound words (viz., Sauf{}laden vs. Sau{}fladen, and Chef{}innenleben vs. Chefinnen{}leben).

Here's my question, which is related to a question I posed in comp.text.tex about half a year ago but which didn't generate any good answers: If one were to compile a list of words for which ligatures should be suppressed, is there a way to program TeX (possibly/likely LuaTeX) not to use ligatures for these words? Please note that I am not looking to suppress ligatures globally for the entire document, but only those that occur in certain words.

Suppose one had a list of words such as

shelf{}ful, self{}ish, self{}less, half{}life, shelf{}life, dwarf{}like, 
waif{}like, wolf{}like, half{}line, roof{}line, Pfaff{}ian, cuff{}link, 
off{}load, off{}line, wolf{}fish, chaf{}finch, saf{}flower

Is there a way to pass this list to TeX and to instruct it to either not insert a ligature at all (as in shelfful and shelflife) or to use only a two-character rather than a three-character ligature (for Pfaffian and offline, say)? Possibly, this no-ligation list would also indicate to TeX the method by which the ligation should be suppressed. To suppress a ligature, one can insert a pair of braces, {}, an italics correction, \/, or a zero-width kern, \kern0pt. However, these methods do not in general produce the same outcome, especially in the case of the fl character pair, and it may therefore be desirable to instruct TeX exactly which non-ligation method it should employ when it encounters a word on the no-ligation list.

By the way, observe that the no-ligation instances for all words in the above list correspond to potential hyphenation points. A pure guess on my part: a solution to the challenge I'm posing may involve creating two types of hyphenation points: the first type would consist of "ordinary" hyphenation points, and instances of the second type would inform TeX that it's dealing with both a hyphenation point and a no-ligation point.

Addendum 16 September — I've added a "bounty" of 200 points. 🙂

And the winning answer is…

I've declared Aditya's entry below the winner. Here's a modified version of his MWE, which contains a (very!!) contrived sentence that contains words with seven different types of ligatures that should be suppressed (f-f, f-i, f-l; ff-i, ff-l; f-fi, and f-fl). Observe that the MWE needs to be compiled with ConTeXt.

\usemodule[translate]

\translateinput[shelffuls][shelf|*|fuls]
\translateinput[selfish][self|*|ish]
\translateinput[halflife][half|*|life] 
\translateinput[standoffish][stand|*|off|*|ish]
\translateinput[cufflinks][cuff|*|links] 
\translateinput[offloads][off|*|loads]    
\translateinput[chaffinches][chaf|*|finches]
\translateinput[safflower][saf|*|flower]

\definetextmodediscretionary * {\prewordbreak\discretionary{-}{}{\kern0pt}\prewordbreak}
\hyphenation{chaf-finches}
\starttext

No ligatures disabled:\\
A standoffish and selfish person who offloads shelffuls 
   of cufflinks and doesn't watch chaffinches in the
   safflower field has a short halflife.

\medskip
\enableinputtranslation 
Some, but not all, ligatures disabled:\\
A standoffish and selfish person who offloads shelffuls 
   of cufflinks and doesn't watch chaffinches in the
   safflower field has a short halflife.

\stoptext

enter image description here

Best Answer

In LuaTeX you can intercept the input and change it before TeX sees it. This can be used to disable certain ligatures. Here is a proof of concept in ConTeXt.

\usemodule[translate]

\translateinput[selfish][self|*|ish]
\translateinput[halflife][half|*|life]

\starttext

A selfish person has a small halflife.

\enableinputtranslation 

A selfish person has a small halflife.

\disableinputtranslation

A selfish person has a small halflife.

\stoptext

which gives

enter image description here

See m-translate.mkiv in the ConTeXt distribution for implementation details. Keep in mind that this will change all occurrences of "selfish" to "self|*|fish", including those in csname. For example, if you have a selfish environment, it will fail! The advantage of this approach is that it will work for all words that contain selfish; for example, selfishness, unselfish, etc.

|*| disables ligatures and does not affect hyphenation. But it introduces a 0.05em kern between the two letters. If you do not like that add

\definetextmodediscretionary *
  {\prewordbreak\discretionary{-}{}{}\prewordbreak}