[Tex/LaTex] How to switch fonts when the language switches in ConTeXt

contextcontext-mkivfontslanguages

I have a mostly English-language document, with fonts defined like this:

\usemodule[simplefonts]
\setmainfont[MinionPro-Regular]
\setsansfont[MyriadPro-Bold]

Occassionally, I need to have some Chinse text appear, using \language[cn]{}, and since the above fonts do not have Chinese characters, I need it to temporarily switch to Adobe Song Std, when Chinese text appears amid the main font, and Adobe Heiti Std, when Chinese text appears in places where sans fonts are used.

  • How can I set ConTeXt to automatically switch fonts when I switch to a different language?

Best Answer

How can I set ConTeXt to automatically switch fonts when I switch to a different language? There might be a misconception involved here as to what \language[foo] actually does. It changes the current hyphenation rules, but is by no means a switch to another script and/or type face.

For this reason your question can be interpreted in (at least) two ways:

  1. You want to be able to use another script once you did a \language[...].
  2. You want a generic language environment switch.

The first variant is best implemented using font fallbacks and will work even without the \language command. This can be considered a cheap and easy solution. The second approach might appear more complicated at the first glance, but it integrates neatly with the overall interface and is extensible with other language related features (text direction, transliteration, emphases etc.) if needed.

Below examples will use Russian for demonstration but should work with any defined language.


The Fallback Method: First we need a list of the codepoint ranges for subsitution, in this case the four defined Cyrillic ranges. The first in the list would already cover contemporary Russian but there’s no reason not to be thorough.

  • 0x0400:0x04ff Cyrillic
  • 0x0500:0x052f Cyrillic Supplement
  • 0x2de0:0x2dff Cyrillic Extended A
  • 0xa640:0xa69f Cyrillic Extended B

They are predefined in Context so we could refer to them by name. Anyways it does not hurt to address them explicitly.

In the example, we will take the fallback glyphs from Computer Modern Unicode which provide a sufficiently close match for Latin Modern. (The CMU fonts are part of TEX Live.) For each of the four varieties regular, italic, bold, bold italic of the serif type face we need a different fallback, because we want Context to substitute glyphs from the appropriate CMU font. After that the fallbacks are ready for use in our typescript, here under the name mainfont.

\definefontfallback [cyrillic_regular]    [name:CMUSerifRoman]
                    [0x0400:0x04ff,0x0500:0x052f,0x2de0:0x2dff,0xa640:0xa69f] [force=yes]
\definefontfallback [cyrillic_bold]       [name:CMUSerifBold]
                    [0x0400:0x04ff,0x0500:0x052f,0x2de0:0x2dff,0xa640:0xa69f] [force=yes]
\definefontfallback [cyrillic_italic]     [name:CMUSerifItalic]
                    [0x0400:0x04ff,0x0500:0x052f,0x2de0:0x2dff,0xa640:0xa69f] [force=yes]
\definefontfallback [cyrillic_bolditalic] [name:CMUSerifBoldItalic]
                    [0x0400:0x04ff,0x0500:0x052f,0x2de0:0x2dff,0xa640:0xa69f] [force=yes]

%% Now we hook the fallbacks into their respective type faces.
\starttypescript [serif] [mainfont]
  \setups[font:fallback:serif]
  \definefontsynonym [Serif]           [name:Latin Modern Roman]             [fallbacks=cyrillic_regular]
  \definefontsynonym [SerifBold]       [name:Latin Modern Roman Bold]        [fallbacks=cyrillic_bold]
  \definefontsynonym [SerifItalic]     [name:Latin Modern Roman Italic]      [fallbacks=cyrillic_italic]
  \definefontsynonym [SerifBoldItalic] [name:Latin Modern Roman Bold Italic] [fallbacks=cyrillic_bolditalic]
\stoptypescript

%% Complete the typescript for all four type faces. Note that we default
%% to the predefined Latin Modern setups for the three other faces.
\starttypescript [mainfont]
 \definetypeface [mainfont] [rm] [serif] [mainfont]     [default]
 \definetypeface [mainfont] [ss] [sans]  [latin-modern] [default]
 \definetypeface [mainfont] [tt] [mono]  [latin-modern] [default]
 \definetypeface [mainfont] [mm] [math]  [latin-modern] [default]
\stoptypescript

%% Finally, we are ready to activate the typescript.
\setupbodyfont [mainfont,20pt]

\starttext

English {\italic English} {\bold English} {\bolditalic English}

\language[ru] %% does only switch the hyphenation pattern
             Маленький мальчик нашел пулемет. Больше в деревне никто не живет. \par
{\italic     Маленький мальчик нашел пулемет. Больше в деревне никто не живет.}\par
{\bold       Маленький мальчик нашел пулемет. Больше в деревне никто не живет.}\par
{\bolditalic Маленький мальчик нашел пулемет. Больше в деревне никто не живет.}\par

\stoptext

Example output


The Macro Method: Just switching the font and hyphenation patterns may suffice for the occasional foreign word, but multilingual typesetting in general will have further requirements, like for instance the different spacing conventions for French. Thus a macro that can be extended on demand is of great value. Below listing gives the skeleton code for a Context-style macro generator \definelazylanguage, which comes with the companion \setuplazylanguage. It allows defining commands that, in this basic form, handle both pattern and font switching. All definitions will create (1) an ordinary macro \foo{...} and (2) the complementing \startfoo / \stopfoo environment. These accept an optional first argument (in brackets, of course) for local setups.

EDIT: Fyi the \installnamespace and \installcorenamespace that make this all possible currently reside in mult-aux.mkiv, see there for further documentation.

ANOTHER EDIT: The following snippet has been revised according to a set of fixes Wolfgang just sent to me. (Many thanks!) It does not only correct a grouping bug but also exploits the namespace functionality more efficiently than the previous version.

% macros=mkvi
\unprotect
\installnamespace                        {lazylanguage}
\installcommandhandler \????lazylanguage {lazylanguage} \????lazylanguage

\appendtoks
  \setuevalue        {\currentlazylanguage}{\lazylanguage_direct[\currentlazylanguage]}%
  \setuevalue{\e!start\currentlazylanguage}{\lazylanguage_start [\currentlazylanguage]}%
  \setuevalue{\e!stop \currentlazylanguage}{\lazylanguage_stop                        }%
\to \everydefinelazylanguage

\unexpanded\def\lazylanguage_direct[#id]%
  {\edef\currentlazylanguage{#id}%
   \dosingleempty\lazylanguage_direct_indeed}

\def\lazylanguage_direct_indeed[#options]%
  {\groupedcommand
     {\lazylanguage_start_indeed[#options]}%
     \donothing}

\unexpanded\def\lazylanguage_start[#id]%
  {\bgroup
   \edef\currentlazylanguage{#id}%
   \dosingleempty\lazylanguage_start_indeed}

% %% This is the macro that implements the core switching functionality.
\def\lazylanguage_start_indeed[#options]%
  {\iffirstargument
     \setupcurrentlazylanguage[#options]%
   \fi
   %% Here would be the place where you can hook further language
   %% settings into your command, imitating the pattern of below
   %% expressions.
   \language[\lazylanguageparameter{patterns}]
   \doifsomething{\lazylanguageparameter{bodyfont}}
    {\switchtobodyfont[\lazylanguageparameter{bodyfont}]}}

\let\lazylanguage_stop\egroup
\protect

\definelazylanguage [lru]
\setuplazylanguage  [lru] [
  bodyfont=antykwa,
  patterns=ru,
]

\startlru
             Маленький мальчик нашел пулемет. Больше в деревне никто не живет. \par
{\italic     Маленький мальчик нашел пулемет. Больше в деревне никто не живет.}\par
{\bold       Маленький мальчик нашел пулемет. Больше в деревне никто не живет.}\par
{\bolditalic Маленький мальчик нашел пулемет. Больше в деревне никто не живет.}\par
\stoplru

English {\italic English} {\bold English} {\bolditalic English}

\stoptext

Example output 2 Note that the code might look confusing at first but that’s because of the multiple levels of wrapping, so it’s actually a feature =). All you need to understand is that the real action is taking place inside the macro \lazylanguage_start_indeed, where you can alway hook further parameters according to the following scheme:

\doifsomething{\lazylanguageparameter{<parametername>}}
  {\lazylanguageparameter{<parametername>}}%

Just replace the <parametername> dummy with a key of your choice and you can use it in the setup.