latex3 / fontspec

Font selection in LaTeX for XeTeX and LuaTeX
http://latex3.github.io/fontspec/
LaTeX Project Public License v1.3c
276 stars 33 forks source link

\@@_declare_shapes_smcaps:nn setup a `slsc -> itsc` substitution #425

Open DamienRobert opened 4 years ago

DamienRobert commented 4 years ago

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

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}
DamienRobert commented 4 years ago

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}},
}
DamienRobert commented 4 years ago

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).

wspr commented 4 years ago

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?

DamienRobert commented 4 years ago

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}
DamienRobert commented 4 years ago

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:

DamienRobert commented 4 years ago

Now here are a few more tricky situations:

DamienRobert commented 4 years ago

(The new push simply correct the commit message, I was using itsc instead of scit...)

DamienRobert commented 4 years ago

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):

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...

wspr commented 4 years ago

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...

DamienRobert commented 4 years ago

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 :)