fontspec
fontspec copied to clipboard
\@@_declare_shapes_smcaps:nn setup a `slsc -> itsc` substitution
If a slanted font is not provided, fontspec setup automatic sl -> it
substitutions in \@@_declare_shape_slanted:nn. But if a small caps
font was provided, an itsc shape is defined but there is no
corresponding slsc -> itsc substitution.
Fix this by adapting the code of \@@_declare_shape_slanted:nn into
\@@_declare_shapes_smcaps:nn.
Status
FOR DISCUSSION (but should be ready)
Todos
- [ ] Tests added to cover new/fixed functionality (Did a manual test)
- [ ] Documentation if necessary (Added developer documentation)
- [X] Code follows expl3 style guidelines
Minimal example demonstrating the new/fixed functionality
\documentclass{article}
\usepackage{fontspec}
\setmainfont{Latin Modern Roman}[
ItalicFeatures = { SmallCapsFont = {Latin Modern Roman Caps/I} },
]
\begin{document}
\fontshape{slsc}\selectfont Slanted Small Caps.
\end{document}
For instance here is my Latin Modern Sans fontspec file.
With this patch I am able to comment all Slanted features and still have the correct sl->it substitutions.
\defaultfontfeatures[Latin Modern Sans]{
Ligatures={TeX},
UprightFont = {*/R},
SmallCapsFont = {Latin Modern Roman Caps},
ItalicFeatures = { SmallCapsFont = {Latin Modern Roman Caps/I} },
BoldFeatures = { SmallCapsFeatures = { FakeBold=4 } },
BoldItalicFeatures = {
SmallCapsFont = {Latin Modern Roman Caps/I},
SmallCapsFeatures = { FakeBold=4 } },
SlantedFont={Latin Modern Sans/I},
BoldSlantedFont={Latin Modern Sans/BI},
SlantedFeatures = { SmallCapsFont = {Latin Modern Roman Caps/I} },
BoldSlantedFeatures = {
SmallCapsFont = {Latin Modern Roman Caps/I},
SmallCapsFeatures = { FakeBold=4 } },
FontFace = {b}{n}{Font = {Latin Modern Sans/B}},
FontFace = {b}{it}{Font = {Latin Modern Sans/BI}},
FontFace = {b}{sl}{Font = {Latin Modern Sans/BI}},
FontFace = {sbc}{n}{Font = {Latin Modern Sans Demi Cond/B}},
FontFace = {sbc}{it}{Font = {Latin Modern Sans Demi Cond/BI}},
FontFace = {sbc}{sl}{Font = {Latin Modern Sans Demi Cond/BI}},
}
I just pushed a new version that also set up a bx/scsl -> bx/scit substitution, and correct the
bx/scit -> b/scit substitution (previously it would substitute to b/it instead).
Thanks! Code changes looks great so far. I can't look into detail right now but will merge once I've wrapped my head around a test file. Before I take a look, does it make sense to you that \fontshape{slsc}\selectfont doesn't work but `\textsl{\textsc{...}}`` does?
It depends on the LaTeX version.
From version 2020-02, LaTeX has a new fontaxe like feature, where we can combine several shapes and setup fallbacks. For your example, the \DeclareFontShapeChangeRule {sl}{sc} {scsl} {scit} applies, we are in sl mode and we request an sc, so the new mode should be scsl, with a fallback to scit if scsl does not exist.
When we directly ask for a scsl shape, since there is no \DeclareFontShapeChangeRule for it, the standard nfss fallback of 'n' kicks in.
This could be corrected directly at the latex level by adding suitable DeclareFontShapeChangeRule {n}{scsl} {scsl} {scit} DeclareFontShapeChangeRule {it}{scsl} {scsl} {scit}... rules. By the way it is a bit painful that the current LaTeX api does not provide a convenient way to ask for scit as a fallback for scsl whatever the current shape we are in now.
In versions prior to 2020-02-02, a \textsl{\textsc{...}} would set up a sc shape, to get a scsl shape we would need to ask for it directly. (Fontspec patches \textsc so that it asks for a scsl shape first, but since it does not exists it fallsback to sc and we are back to the standard LaTeX situation.)
Note that there is a similar fallback to 'b' for the 'bx' series:
\DeclareFontSeriesChangeRule {m}{bx} {bx} {b}
\DeclareFontSeriesChangeRule {b}{bx} {bx} {b} %<-----
\DeclareFontSeriesChangeRule {c}{bx} {bx} {b} %<-----
\DeclareFontSeriesChangeRule {ec}{bx} {bx} {b} %<-----
\DeclareFontSeriesChangeRule {sc}{bx} {bx} {b} %<-----
\DeclareFontSeriesChangeRule {l}{bx} {bx} {b} %<-----
And a it fallback for the sl shape:
\DeclareFontShapeChangeRule {n}{sl} {sl} {it}
\DeclareFontShapeChangeRule {it}{sl} {sl} {it}
Now it gets tricky. With the current way fontspec does things, it processes custom FontFace first before processing the m/n, b/n, m/it, m/sl, b/it, b/sl faces (in this order, cf \@@_set_faces).
When fontspec set up a face, it also set up the sc shape, the sl shape if the shape is it, and the bx weight if the weight is b. This is not a problem for sl since the sl calls are after the it calls, but this is a problem for bx.
For instance in my LatinModernRoman fontspec file, I want
BoldFont = {* Demi/B},
BoldItalicFont = {* Demi/BI},
FontFace = {bx}{n}{Font = {*/B}},
FontFace = {bx}{it}{Font = {*/BI}},
because that is also what tulmr.fd does.
But since the FontFace is processed first, the automatic bx -> b substitution set up by fontspec when it process the bold font erases my custom bx FontFace.
Indeed, patching DeclareFontShape to add logging, I get:
LaTeX Info: DeclareFontShape: TU/LatinModernRoman(0)/bx/n=<->"LatinModernRoman/B
:mode=node;script=latn;language=dflt;+tlig;" () on input line 81.
LaTeX Info: DeclareFontShape: TU/LatinModernRoman(0)/bx/n=<->ssub*LatinModernRom
an(0)/b/n () on input line 81.
There are two solutions:
- either use a different version of
\@@_DeclareFontShapein\@@_declare_shapes_bxthat checks first if thebxweight exists (by testing if the csname#1/#2/#3/#4exists) and in this case do nothing. - or (for LaTeX version
2020-02) use the existingbx -> bandsl -> itfallbacks and remove\@@_declare_shape_slantedand\@@_declare_shapes_bx.
Now here are a few more tricky situations:
- because of the
sl -> itsubstitution set up by fontspec, if a user wants to setup both a egl/slFontFace and al/itFontFace, then the order mater, thel/slFontFace has to be put afterwards so that it does not get overwritten by thel/itone. Once again this could be solved by checking first for an existingslshape before doing the substitution, or by relying on the new LaTeX fallback. \@@_merge_shape:nis somewhat redundant with the new LaTeX version. Here is what really happens with\textsl{\textsc{...}}: first\@@_merge_shape:nkicks in, combine theslshape with the existingscshape and checks if thescslshape exists. Since it does not it fallsback to\fontshape{sl}where the new LaTeX shape substitution now kicks in.- For
\sishape, since it calls\fontshape{\scitdefault}, ie it directly setsscit, the existingscit -> scslfallback does not kick in (LaTeX set up both way fallbacksscit <-> scsl). This could be solved by calling\fontshape{\scdefault}\fontshape{\itdefault}instead (or better on the LaTeX side by extending\DeclareFontShapeChangeRuleto have a globalscit -> scslfallback).
(The new push simply correct the commit message, I was using itsc instead of scit...)
A few last thoughts on this subject of bx->b substitution (this is unrelated to the pr itself, sorry I probably should have made a separate bug report):
- relying on the new LaTeX fallback instead has some odd corner cases too. For instance if only
bx/mandbx/itare defined, then\fontseries{bx}\fontshape{sc}will givebx/mbut\fontshape{sc}\fontseries{bx}will giveb/sc. On the other hand it is not clear which would be best anyway. - puting the
FontFacedefinitions after the setup of the normal font, italic font and so on, rather than before like it is currently the case, so thatbxfontfaces are not overwritten is not quite possible because of the automaticschandling. For instance setting up anm/uishape (like in lmr) would also set up them/scshape and we would not want to overwrite the normalm/scfont. - For the automatic handling of the
scfont, we cannot rely on the new latex behavior anyway because it is not simply a substitution. But this mean that a user setting up asb/scfontshape before asb/nfontshape will have it overwritten. So I wonder if checking first if thescshape exists before overwriting it could help. The problem from this approach is that\addfontfeaturesrely on being able to overwrite existing shape settings...
So unfortunately I don't see any easy solution to solve all corner cases. What I currently do is patch fontspec to remove the sl->it and bx->b substitutions and rely instead on the LaTeX 2020-02 fallbacks, patch LaTeX to add globals scsl -> scit and bx->b substitutions (rather than only the specific existing ones), and be careful in my fontface declarations order for small caps fonts...
Thanks much for all the careful work here. FWIW I am more than happy that any new code in fontspec assumes a 2020 version of LaTeX. I’m in the middle of a busy period at work but I hope I can look at this code sometime soon...
Ok, so if we are allowed to assume LaTeX 2020, then my feeling is that the best would simply to remove \@@_declare_shape_{slanted,bx} and let the new LaTeX's fallback work their substitutions instead.
In this case this pull-request that correct some bugs for both these functions is no longer needed ;)
I am happy to provide another pull request that does this if you agree. As I argued (too lengthy) above, this is not the perfect solution but imho the least worse one. Maybe you have better ideas than me!
Anyway there is no hurry, meanwhile I am happy with simply patching fontspec in fontspec.cfg :)