निरंजन
I am trying to setup a key-value interface for selecting fonts for different fields. I am using `expkv-def` along with some l3 functions. Here is what I develop keys:
```
\documentclass{article}
\usepackage{fontspec}
\usepackage{expkv-def}
\makeatletter
\ekvdefinekeys{foo}{%
code title = {%
\def\my@field{title}%
\ekvset{bar}{#1}%
\fontsettingcmd
},%
code subtitle = {%
\def\my@field{subtitle}%
\ekvset{bar}{#1}%
\fontsettingcmd
}%
}
\ekvdefinekeys{bar}{%
code rm = {%
\def\my@type{rm}%
\my@fnt@processor{#1}%
},%
code sf = {%
\def\my@type{sf}%
\my@fnt@processor{#1}%
},%
code tt = {%
\def\my@type{tt}%
\my@fnt@processor{#1}%
}%
}
\NewDocumentCommand \testfoo { m }{%
\ekvset{foo}{#1}%
}
```
The LaTeX3 code for interpreting this data is as follows:
```
\ExplSyntaxOn
\seq_new:N \l__my_fnt_type_seq
\cs_new:Npn \my@fnt@processor #1 {
\exp_after:wN \newfontfamily \cs:w
my @ \my@field @ \my@type @ fnt
\cs_end: [
NFSSFamily = {
my @ \my@field @ \my@type @ nfss
}
]{ #1 }
\seq_put_right:Ne \l__my_fnt_type_seq { \my@type }
}
\cs_new:Npn \fontsettingcmd {
\cs_gset:cpn { my @ \my@field @ fnts } {
% As per https://tex.stackexchange.com/a/729805,
% we need to generate the following macros:
% \def\rmdefault{my@title@rm@nfss}
% \def\sfdefault{my@title@sf@nfss}
% \def\ttdefault{my@title@tt@nfss}
% Doesn't work, as of now.
\seq_map_inline:Nn \l__my_fnt_type_seq {
\cs_gset:cpe { ####1default } {
my @ \my@field @ ####1 @ nfss
}
}
\normalfont
}
}
\ExplSyntaxOff
\makeatother
```
The objective is to make something like following work:
```
\begin{document}
\testfoo{%
title = {%
rm = {%
NewCM10-Book.otf%
},%
sf = {%
NewCMSans10-Book.otf%
},%
tt = {%
NewCMMono10-Book.otf%
}%
},%
subtitle = {%
rm = {%
KpRoman-Regular.otf%
},%
sf = {%
KpSans-Regular.otf%
},%
tt = {%
KpMono-Regular.otf%
}%
}%
}
\makeatletter
\my@title@fnts
\makeatother
\setlength{\parindent}{0pt}
{%
\rmfamily
\meaning\rmdefault
test%
}
{%
\sffamily
\meaning\sfdefault
test%
}
{%
\ttfamily
\meaning\ttdefault
test%
}
\end{document}
```
As of now the families are set only for the last field, i.e., in this case `subtitle`. I need to eagerly expand the value `\my@field`, I tried `f` also instead of `e`, but no luck. What should I do?
Top Answer
Skillmon
There are a few issues in your code. Let's list them :)
- I'd not mix 2e-style internal code with L3, use `\ekvdefinekeys` inside the L3 scope instead of defining 2e-style macros in the L3 layer.
- `\exp_after:wN \<function> \cs:w <name> \cs_end:` is really bad L3 code, use expansion control where possible, only resort to `\exp_after:wN` and `\cs:w` in optimised low level code or where not otherwise possible. So use `\exp_args:Nc \<function> { <name> }` or, if `\<function>` is an L3 function use `\cs_generate_variant:Nn` on it and use the variant.
- (as already mentioned by frougon) you need to expand `\my@field` when assigning to your internal inside of `\fontsettingcmd` (my code below uses the fully expandable `\seq_map_function:NN` instead of `\seq_map_inline:Nn`, and expands the variable, the result is that in my code your font-setting macro only contains `\cs_gset:Npn \rmdefault {my@nfss@title@rm}...`.
Since your key codes are essentially identical I'd define them via a loop (and if you only need `code` you don't need `expkv-def`, so the following uses the basic `expkv`-syntax).
```
\documentclass{article}
\usepackage{fontspec}
\usepackage{expkv}
\ExplSyntaxOn
% key definition
\tl_new:N \l__my_field_tl
\tl_new:N \l__my_type_tl
\clist_map_inline:nn { title, subtitle }
{
\protected\ekvdef { foo } {#1}
{
\tl_set:Nn \l__my_field_tl {#1}
\ekvset { bar } {##1}
\__my_define_fnt_setting_cmd:
}
}
\clist_map_inline:nn { rm, sf, tt }
{
\protected\ekvdef { bar } {#1}
{
\tl_set:Nn \l__my_type_tl {#1}
\__my_fnt_processor:n {##1}
}
}
\protected\ekvsetdef\testfoo{foo}
% font loading
\seq_new:N \l__my_fnt_type_seq
\cs_new_protected:Npn \__my_fnt_processor:n #1
{
\exp_args:Nc \newfontfamily { __my_fnt_ \l__my_field_tl _ \l__my_type_tl : }
[
NFSSFamily = { my@nfss@ \l__my_field_tl @ \l__my_type_tl }
]
{#1}
\seq_put_right:NV \l__my_fnt_type_seq \l__my_type_tl
}
% define font setup command
\cs_new_protected:Npn \__my_define_fnt_setting_cmd:
{
\cs_gset:cpx { __my_fnts_ \l__my_field_tl : }
{
\seq_map_function:NN
\l__my_fnt_type_seq \__my_define_fnt_setting_cmd_aux:n
}
}
\cs_new:Npn \__my_define_fnt_setting_cmd_aux:n #1
{
\cs_gset:Npn \exp_not:c { #1 default } { my@nfss@ \l__my_field_tl @ #1 }
}
% frontend macro to picking fonts
\msg_new:nnn { my } { unknown-font-field }
{ Unknown~ font~ field~ #1.~ Maybe~ you~ misspelled~ it. }
\NewDocumentCommand \setmyfonts { m }
{
\cs_if_exist_use:cF { __my_fnts_ #1 : }
{ \msg_error:nnn { my } { unknown-font-field } {#1} }
}
\ExplSyntaxOff
\testfoo
{
title = {
rm = NewCM10-Book.otf,
sf = NewCMSans10-Book.otf,
tt = NewCMMono10-Book.otf
},
subtitle = {
rm = KpRoman-Regular.otf,
sf = KpSans-Regular.otf,
tt = KpMono-Regular.otf
}%
}
\expandafter\show\csname __my_fnts_title:\endcsname
\setmyfonts{title}
\begin{document}
{%
\rmfamily
\meaning\rmdefault
test%
}
{%
\sffamily
\meaning\sfdefault
test%
}
{%
\ttfamily
\meaning\ttdefault
test%
}
\end{document}
```
Answer #2
frougon
You should use things like `\show\my@title@fnts` to debug your code. This would reveal that your macro:
```
\cs_new:Npn \fontsettingcmd {
\cs_gset:cpn { my @ \my@field @ fnts } {
% As per https://tex.stackexchange.com/a/729805,
% we need to generate the following macros:
% \def\rmdefault{my@title@rm@nfss}
% \def\sfdefault{my@title@sf@nfss}
% \def\ttdefault{my@title@tt@nfss}
% Doesn't work, as of now.
\seq_map_inline:Nn \l__my_fnt_type_seq {
\cs_gset:cpe { ####1default } {
my @ \my@field @ ####1 @ nfss
}
}
\normalfont
}
}
```
doesn't work as you expect, because there is nothing to expand the last `\my@field` before the `\cs_gset:cpn` is executed.
When `\fontsettingcmd` is defined, it has two `\my@field` tokens in its replacement text. The first one is expanded when `\cs_gset:cpn { my @ \my@field @ fnts } { … }` is executed due to the `c` argument type; the second one becomes part of the replacement text of the `\my@....@fnts` macro defined at this moment. And that is the problem: in order to work correctly, instead of this `\my@field` token, your code would need the *replacement text* of `\my@field` at the moment `\fontsettingcmd` is executed. You did try to expand it with `\cs_gset:cpe`, but that one is executed way too late.
You could use `\cs_gset:cpx` or `\cs_gset:cpe`, but that would probably result in not very readable code. A simple and readable way to solve the problem is to make `\fontsettingcmd` take an argument which gives the current replacement text of `\my@field`, i.e. `title` or `subtitle` in your examples (of course, we could derive it from `\my@field` in the `\ekvdefinekeys{foo}{...}`).
Besides, I define `\fontsettingcmd` with `\cs_new_protected:Npn` because it can't be expandable since it performs an assignment (a macro assignment). Ditto for `\my@fnt@processor`, as `\newfontfamily` and `\seq_put_right:NV` are not expandable. Also, I use
```
\seq_put_right:NV \l__my_fnt_type_seq \my@type
```
because we only need one expansion of `\my@type` here (`\seq_put_right:No \l__my_fnt_type_seq { \my@type }` would work as well).
You're lucky that `\newfontfamily` appears to expand its optional argument!
I propose to replace the IMHO ugly
```
\cs_new:Npn \my@fnt@processor #1 {
\exp_after:wN \newfontfamily \cs:w
my @ \my@field @ \my@type @ fnt
\cs_end: [
NFSSFamily = {
my @ \my@field @ \my@type @ nfss
}
]{ #1 }
\seq_put_right:Ne \l__my_fnt_type_seq { \my@type }
}
```
with
```
\cs_new_protected:Npn \__my_new_font_family:Nnn #1#2#3
{ \newfontfamily {#1} [#2] {#3} }
\cs_generate_variant:Nn \__my_new_font_family:Nnn { ce }
\cs_new_protected:Npn \__my_process_font:n #1
{
\__my_new_font_family:cen { my @ \my@field @ \my@type @ fnt }
{ NFSSFamily = { my @ \my@field @ \my@type @ nfss } }
{#1}
\seq_put_right:NV \l__my_font_type_seq \my@type
}
```
The `c`-type argument allows us to get rid of the ugly `\exp_after:wN \newfontfamily \cs:w my @ \my@field @ \my@type @ fnt \cs_end: `; my function uses a name that is in line with expl3 conventions (form + imperative style); and the `e`-type argument allows us not to rely on the lucky fact that `\newfontfamily` expands its optional argument.
Similar thing for the other function `\fontsettingcmd`. The expl3 names are made accessible outside `\ExplSyntaxOn ... \ExplSyntaxOff` with this:
```
\cs_new_eq:NN \my@process@font \__my_process_font:n
\cs_new_eq:NN \my@set@fonts \__my_set_fonts:n
```
I assign `\rmdefault` and friends locally rather than globally (why would you want this global?).
I also fixed indentation according to `texdoc l3styleguide`. Please, pretty please, read this short document.
Final remark: please leave at least a blank line between function definitions for readability.
```
\documentclass{article}
\usepackage{fontspec}
\usepackage{expkv-def}
\makeatletter
\ekvdefinekeys{foo}{%
code title = {%
\def\my@field{title}%
\ekvset{bar}{#1}%
\my@set@fonts{title}%
},%
code subtitle = {%
\def\my@field{subtitle}%
\ekvset{bar}{#1}%
\my@set@fonts{subtitle}%
}%
}
\ekvdefinekeys{bar}{%
code rm = {%
\def\my@type{rm}%
\my@process@font{#1}%
},%
code sf = {%
\def\my@type{sf}%
\my@process@font{#1}%
},%
code tt = {%
\def\my@type{tt}%
\my@process@font{#1}%
}%
}
\NewDocumentCommand \testfoo { m } {%
\ekvset{foo}{#1}%
}
\ExplSyntaxOn
\cs_new_protected:Npn \__my_new_font_family:Nnn #1#2#3
{ \newfontfamily {#1} [#2] {#3} }
\cs_generate_variant:Nn \__my_new_font_family:Nnn { ce }
\seq_new:N \l__my_font_type_seq
\cs_new_protected:Npn \__my_process_font:n #1
{
\__my_new_font_family:cen { my @ \my@field @ \my@type @ fnt }
{ NFSSFamily = { my @ \my@field @ \my@type @ nfss } }
{#1}
\seq_put_right:NV \l__my_font_type_seq \my@type
}
\cs_new_protected:Npn \__my_set_fonts:n #1
{
\cs_gset:cpn { my @ #1 @ fnts }
{
\seq_map_inline:Nn \l__my_font_type_seq
{
\cs_set:cpn { ####1default } { my @ #1 @ ####1 @ nfss }
}
\normalfont
}
}
\cs_new_eq:NN \my@process@font \__my_process_font:n
\cs_new_eq:NN \my@set@fonts \__my_set_fonts:n
\ExplSyntaxOff
\makeatother
\begin{document}
\testfoo{%
title = {%
rm = {%
NewCM10-Book.otf%
},%
sf = {%
NewCMSans10-Book.otf%
},%
tt = {%
NewCMMono10-Book.otf%
}%
},%
subtitle = {%
rm = {%
KpRoman-Regular.otf%
},%
sf = {%
KpSans-Regular.otf%
},%
tt = {%
KpMono-Regular.otf%
}%
}%
}
\makeatletter
% \show\my@title@fnts
% \show\my@subtitle@fnts
\my@title@fnts
\makeatother
\setlength{\parindent}{0pt}
{%
\rmfamily
\meaning\rmdefault
test%
}
{%
\sffamily
\meaning\sfdefault
test%
}
{%
\ttfamily
\meaning\ttdefault
test%
}
\end{document}
```
![image.png](/image?hash=d21f6d5a30c1ab8895be421ca9d8ecc8193c918551ea4e1d42c24de1380f8eec)