10

This question concerns two attributes of fonts in LaTeX: series (e.g. bold extended) and shape (e.g. italic small-caps). The problem does not arise for encoding (e.g. T1) or family (e.g. yesjw).

How can I do one of the following two things?

  1. Determine whether the currently active font has been substituted by the kernel (or similar) and, if it has, which font has been substituted. I do not want to include substitutions defined using the macros characteristic of .fd files. (It would be OK if I caught these, too, but I don't especially want to.)
  2. Determine the real attributes of the currently active font. This is straightforward for encoding and family, but I can't figure out how to do it reliably for series or shape.

I think I must be missing something obvious, but I just can't see it. That is, I can think of ways to do one or other of the above, but I can't think of ways which play nicely with anything else at all. For example, I could undefine the current font and then try reinitialising it, resetting it to some default in case of failure, but that seems akin to bombing an anthill: unduly costly, unnecessarily destructive and of questionable effectiveness.

To see why somebody might want to determine this, consider the following MWE:

\documentclass{article}
\pagestyle{empty}
\begin{document}
\makeatletter

\def\exfs@existence#1/#2/#3/#4\@nil{#1/#2/#3/#4 : \ifcsname #1/#2/#3/#4\endcsname existent\else nonexistent\fi}
\def\exfs@curr@existence{\expandafter\exfs@existence\curr@fontshape\@nil}

\exfs@curr@existence

\scshape\exfs@curr@existence

\upshape

\exfs@existence\f@encoding/\f@family/ec/\f@shape\@nil

\exfs@curr@existence

\fontseries{ec}\selectfont
\exfs@curr@existence

\exfs@existence\f@encoding/\f@family/el/\f@shape\@nil

\exfs@curr@existence

\fontseries{el}\selectfont
\exfs@existence\f@encoding/\f@family/el/\f@shape\@nil\marginpar{??!!}

\exfs@curr@existence\marginpar{??!!}


\exfs@existence\f@encoding/\f@family/\f@series/sc\@nil

\scshape
\exfs@existence\f@encoding/\f@family/\f@series/sc\@nil\marginpar{??!!}

\exfs@curr@existence\marginpar{??!!}

\end{document}

result of font shenanigans

Note that requesting a switch to a non-existent series orthogonal to the current one is handled gracefully and it remains straightforward to retrieve the font attributes.

But requesting a switch to a non-existent series congruent with the current one causes chaos. I'm not particularly bothered about this. In particular, I do not think it is a bug unless the developers think so. But I am bothered by my inability to reliably catch it. In particular, I'd like to avoid providing commands which fail or report spurious success when they encounter it.

Note that it isn't necessary to use anything as esoteric as \fontshape{el}\selectfont to see the problem. Something as innocuous as

\normalfont\scshape
\exfs@curr@existence

\bfseries
\exfs@curr@existence\marginpar{??!!}

demonstrates the basic issue, even though the typeset result is arguably correct.

basic issue with standard font macros

Note that this question is in relation to package support for 'traditional' fonts, so LuaTeX etc. is definitely out.

[Don't know if this is 'inner enough' for latex-base, but there doesn't seem to be a better tag available?]

4
  • 1
    If a font combination, say OT1/cmr/el/sc isn't defined in the environment (usually by the .fd file), \ExpandArgs{c}\show{OT1/cmr/el/sc} will tell you that this is \relax. But if you do something that requests that nonexistent font, a substitution will happen and the (internal) control sequence \OT1/cmr/el/sc will be made equivalent to the one corresponding to the substitute font.
    – egreg
    Commented Jun 16 at 7:57
  • @egreg Thanks. That's essentially what I use in the case of series to stop e.g. \elweight creating a font.
    – cfr
    Commented Jun 16 at 18:18
  • If there's no straightforward way to do it, I don't know if the package's commands should behave as much like kernel commands as possible or if they should as far as possible not create/use fictitious fonts.
    – cfr
    Commented Jun 16 at 19:07
  • @egreg Is it worth doing this (e.g. using something like the answer below) or should I just treat any existent font as real and blame somebody else if it doesn't work? And maybe let shapes create fictions, even ....
    – cfr
    Commented Jun 17 at 5:24

1 Answer 1

8

We can hook the internal LaTeX \wrong@fontshape macro to get a mapping between the requested font and the substituted font, then add a hook to selectfont to check to see if the current font was substituted or not:

%% SPDX-License-Identifier: CC0-1.0
%% Initialization
\documentclass[12pt]{article}

\makeatletter\ExplSyntaxOn

%% Private Variables
\str_new:N \l__example_before_font_str
\str_new:N \l__example_after_font_str
\prop_new:N \g__example_font_substitution_prop

%% Public Variables
\str_new:N \l_example_substituted_font_str

%% Hook the \wrong@fontshape command to map font substitutions
\hook_gput_code:nnn { cmd / wrong@fontshape / before } { example } {
    \str_set:Ne \l__example_before_font_str { \curr@fontshape }
}

\hook_gput_code:nnn { cmd / wrong@fontshape / after } { example } {
    \str_set:Ne \l__example_after_font_str { \curr@fontshape }
    \prop_gput:NVV \g__example_font_substitution_prop \l__example_before_font_str \l__example_after_font_str
}

%% Hook the \selectfont command to see if the font has been substituted
\hook_gput_code:nnn { selectfont } { example } {
    \str_set:Ne \l__example_before_font_str { \curr@fontshape }
    \prop_get:NVNTF \g__example_font_substitution_prop \l__example_before_font_str \l__example_after_font_str {
        \str_set_eq:NN \l_example_substituted_font_str \l__example_after_font_str
    } {
        \str_clear:N \l_example_substituted_font_str
    }
}

%% Define a user command to see if the font has been substituted
\NewDocumentCommand \isfontsubstituted { } {
    \str_if_empty:NTF \l_example_substituted_font_str {
        existent
    } {
        NONEXISTENT ~ $\to$ ~ \str_use:N \l_example_substituted_font_str
    }
}

%% Define a user command to show the current font and if it has been substituted
\NewDocumentCommand \showfontsubstitution { } {
    \curr@fontshape :~ \isfontsubstituted
}

\ExplSyntaxOff\makeatother

%% Demonstration
\pagestyle{empty}
\setlength{\parindent}{0pt}

\begin{document}
    \showfontsubstitution

    {\scshape \showfontsubstitution}

    {\bfseries \showfontsubstitution}

    {\scshape \bfseries \showfontsubstitution}

    {\itshape \showfontsubstitution}

    {\itshape \bfseries \showfontsubstitution}

    {\slshape \bfseries \showfontsubstitution}

    {\scshape \slshape \showfontsubstitution}

    {\scshape \itshape \showfontsubstitution}

    {\fontseries{el}\selectfont \showfontsubstitution}

    {\scshape \fontseries{el}\selectfont \showfontsubstitution}
\end{document}

output

I think that \scshape \itshape showing up as “existent” is what you wanted, even though it is technically mapped to \scshape \slshape. The case \scshape \fontseries{el} correctly shows up as “NONEXISTENT”, although it shows as mapped to the also-nonexistent \fontseries{el}. However, \fontseries{el} does map to \normalfont, so if you follow the chain of mappings, you'll eventually get back to the real font.


An alternative solution is to redefine \wrong@fontshape to prevent LaTeX from substituting any fonts and force the user to fix their error:

%% SPDX-License-Identifier: CC0-1.0
%% Initialization
\documentclass{article}

\makeatletter

%% Define a command to force an error if a font is not available
\let\saved@wrong@fontshape=\wrong@fontshape

\def\skip@wrong@fontshape{%
    \PackageError{example}{%
        Font ``\expanded{\csname\curr@fontshape\endcsname}'' is not available.%
        \MessageBreak Switching to an emergency fallback font.%
    }{%
        Your current font likely does not support the requested style.%
        \MessageBreak Either remove the unsupported font switch, or%
        \MessageBreak switch to a font that supports the requested style.%
    }%
    \switch@to@error@font%
}

%% Use italic CM typewriter if we get an error so that it can't be ignored
\def\switch@to@error@font{%
    \error@fontshape%
    \def\f@family{cmtt}%
    \def\f@shape{it}%
    \xdef\font@name{\csname\curr@fontshape\endcsname}%
    \saved@selectfont%
}

%% Define a command to select a font without substitutions
\let\saved@selectfont=\selectfont

\def\selectfont@no@substitutions{%
    \let\wrong@fontshape=\skip@wrong@fontshape%
    \saved@selectfont%
    \let\wrong@fontshape=\saved@wrong@fontshape%
}

%% Overwrite the \selectfont command to never substitute fonts
\let\selectfont=\selectfont@no@substitutions

\makeatother

%% Demonstration
\pagestyle{empty}
\setlength{\parindent}{0pt}

\begin{document}
    abc {\scshape def {\bfseries ghi}} jkl {\bfseries mno {\scshape pqr}} stu
\end{document}

output

5
  • Thanks! I've thought about hooks, but I expected to find I was missing something basic. If I use this, may I use it in a package published under the LPPL?
    – cfr
    Commented Jun 16 at 18:33
  • 1
    @cfr Sure, that's fine with me. Commented Jun 17 at 0:07
  • 1
    Thanks. I think what I can't decide is what the right thing to do is, independently of how to do it. At least, it always seems to come back to that question.
    – cfr
    Commented Jun 17 at 0:14
  • @cfr Well, it's arguably an error for a user to select a non-existent font, so an alternative solution is to print and error message and force the font to something really ugly so that the user can't just ignore the error (see the edit). A less aggressive method would be to override the logic in \wrong@font so that if the user requests extra-bold slanted, then try extra-bold italic and bold slanted instead of just falling back to the defaults. Commented Jun 17 at 6:01
  • 1
    Yes, but I hate to think what Frank and Ulrike will do to me if I do that ;).
    – cfr
    Commented Jun 17 at 6:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .