9

I've created an environment for which various parameters are set through key values. Instead of creating a hornet's nest of various booleans, I would just like to have a slight variant form for some of the keys to handle some of the formatting.

In particular, while none of the keys are mandatory, I need a way of handling things if I omit some of the keys. So, I figured I'd create starred variants.

To help you understand what I'm trying to do in the following MWE, here's the basic idea: I have three optional tags to add to a quote (title, description, and author). Each is meant to show up on its own line, when used. If the key is not used, then nothing should be done. But if the author key is not called, then I need a means of handling the line breaks to avoid unwanted extra whitespace. The starred variants are to be used in such a situation.

Here's a MWE:

\documentclass{article}
\newcommand{\attrib}[1]{\nopagebreak{\par\raggedleft\footnotesize #1\par}}
%%----------
\makeatletter
%%----------
\let\ae@source@title\relax
\let\ae@source@description\relax
\let\ae@source@author\relax
%%----------
\newlength{\widthofwidestline}
\newlength{\afterEpigraphSkip}
\setlength{\widthofwidestline}{3in}
\setlength{\afterEpigraphSkip}{1\baselineskip}
%%----------
\RequirePackage{pgfkeys}
\pgfkeys
  {
    /ae/my/epigraph/environment/set/options/.cd,
    title/.code         = {\def\ae@source@title{#1\\}},
    description/.code   = {\def\ae@source@description{#1\\}},
    author/.code        = {\def\ae@source@author{#1}},
%% variants for handling `title` and `description` when `author`
%% (and possibly other keys) are missing.
    title*/.code         = {\def\ae@source@title{#1}},
    description*/.code   = {\def\ae@source@description{#1}},
%% length parameters
    longest line/.code  = {\settowidth{\widthofwidestline}{#1}},
    afterskip/.code     = {\setlength{\afterEpigraphSkip}{#1}},
  }
\newenvironment{epigraph}[1]
  {\pgfkeys{ /ae/my/epigraph/environment/set/options/.cd, #1 }%
   \par\hspace*{\fill}%
   \begin{minipage}[t]{\dimexpr\widthofwidestline+1em}%
  }
  {\attrib{%
     \ae@source@title
     \ae@source@description
     \ae@source@author
     \makebox[0pt][r]{\rule[-0.8pt]{1in}{0.4pt}}%
    }%
   \end{minipage}%
   \hspace*{\fill}%
   \vspace{\afterEpigraphSkip}%
  }
\makeatother

\pagestyle{empty}
\begin{document}

\begin{epigraph}{title={title},
                 description={description},
                 author={me},
                 longest line=abcdefghijklm}
  Scenario 1
\end{epigraph}

\begin{epigraph}{author={me},
                 longest line=abcdefghijklm}
  Scenario 2
\end{epigraph}

\begin{epigraph}{title={title},
                 description={description},
                 longest line=abcdefghijklm}
  Scenario 3
\end{epigraph}

\begin{epigraph}{title={title},
                 description*={description},
                 longest line=abcdefghijklm}
  Scenario 4
\end{epigraph}

\begin{epigraph}{title*={title},
                 longest line=abcdefghijklm}
  Scenario 5
\end{epigraph}

\begin{epigraph}{description*={description},
                 longest line=abcdefghijklm}
  Scenario 6
\end{epigraph}

\end{document}

Scenario 3 illustrates what I want to avoid. This MWE produces:

enter image description here

I'm not so much interested in creating a new epigraph command: I know there are such packages. I also know that I could set and process booleans to handle this issue automatically. But as I mentioned, I really would like to avoid creating boolean switches.

I'm more interested in a larger issue where key values are being used, but where occasionally you might want to handle some of the keys in a slightly different manner than usual: hence the starred-variant.

I haven't found anything in the documentation that would allow for this. I also didn't find anything on this site related to starred variants of keys.

I'm primarily interested in feedback about best practices in such a situation.

  • You could define title/.style={title*={#1\\}}. By the way, is there a reason you use .code handlers that simply define a macro? You could just use an actual value-key (handler: .initial) and reduce the overhead. – Qrrbrbirlbel Jul 22 '13 at 19:32
  • @Qrrbrbirlbel I don't think I understand how your .style method would work. Could you elaborate? – A.Ellett Jul 22 '13 at 19:36
  • @Qrrbrbirlbel I'm very new to using key-values. It seemed to me that I was doing more than just defining a value, but also setting up additional formatting. Hence the use of .code. If you could illustrate how to avoid making so many calls via .code, I'd be very interested. After all, I'm more interested in best practices here than just some hack that gets the job done. – A.Ellett Jul 22 '13 at 19:41
  • The only difference between title and title* that I see is that the un-starred version includes a final \\. If you define title as a key that simply forwards its argument appended by \\ to title*. On the other hand, I would actually post-process the values so that the user doesn’t need to work so much; of course, this would involve some checking for empty values and is probably depending on how complex the input scenarios can get. – Qrrbrbirlbel Jul 22 '13 at 19:43
  • @Qrrbrbirlbel With regard to the title key, are you suggesting something more along the lines of title*/.initial=#1, followed by title/.style={title*={#1\\}}? – A.Ellett Jul 22 '13 at 19:43
4

I'm using actual value-keys. These are setup with the .initial handler (every value has an initial value). This reduces overhead, in my opinion. As it just a wrapper for a \csname solution. A key of the path /tree/sub/name is stored in the macro \pgfk@/tree/sub/name. I don't see much of the reason why I'd define a key that simply sets another family of macros.

The macros starting with \@epi@… in the environment are merely temporarily macros since they are only defined in the environment (which groups its content).

Code

\documentclass{article}
\newcommand{\attrib}[1]{\nopagebreak{\par\raggedleft\footnotesize #1\par}}
%%----------
\makeatletter
%%----------
\newlength{\widthofwidestline}
\newlength{\afterEpigraphSkip}
\setlength{\widthofwidestline}{3in}
%%----------
\RequirePackage{pgfkeys}
\def\epigraphset{\pgfqkeys{/ae/my/epigraph/environment/set/options}}
\def\epigraphgetvalue#1{\pgfkeysgetvalue{/ae/my/epigraph/environment/set/options/#1}}
\epigraphset{
    title/.initial      =,
    description/.initial=,
    author/.initial     =,
    longest line/.code  = {\settowidth{\widthofwidestline}{#1}},
    afterskip/.code     = {\setlength{\afterEpigraphSkip}{#1}},
    afterskip=1\baselineskip
  }
\newenvironment{epigraph}[1]{
  \epigraphset{#1}%
  \par\hspace*{\fill}%
  \begin{minipage}[t]{\dimexpr\widthofwidestline+1em}%
}{
  \attrib{%
     \epigraphgetvalue{title}\@epi@title
     \epigraphgetvalue{description}\@epi@desc
     \epigraphgetvalue{author}\@epi@author
%     \noindent % needed (?) if you use \\
     \@epi@title
     \ifx\@epi@desc\pgfkeys@empty\else
       \par\@epi@desc
     \fi
     \ifx\@epi@author\pgfkeys@empty\else
       \par\@epi@author
     \fi
     \makebox[0pt][r]{\rule[-0.8pt]{1in}{0.4pt}}%
  }%
  \end{minipage}%
  \hspace*{\fill}%
  \vspace{\afterEpigraphSkip}%
}
\makeatother

\pagestyle{empty}
\begin{document}
\epigraphset{longest line=abcdefghijklm}
\begin{epigraph}{title=title,description=description,author=me}
  TDA
\end{epigraph}

\begin{epigraph}{author=me}
  \_\_A
\end{epigraph}
\begin{epigraph}{description=description}
  \_D\_
\end{epigraph}
\begin{epigraph}{title=title}
  T\_\_
\end{epigraph}

\begin{epigraph}{title=title,description=description}
  TD\_
\end{epigraph}
\begin{epigraph}{title=title,author=author}
  T\_A
\end{epigraph}
\begin{epigraph}{description=description,author=author}
  \_DA
\end{epigraph}

\end{document}

Output

enter image description here

  • Thank you. This helps me understand quite a few details about pgfkeys that were murky to me at best. But I do still have another question. According to my understanding of pgfkeysgetvaule{<key>}{<macro>}---as explained in the manual---I would have attempted to run a test as \ifx\@epi@title\relax\else....\fi. But this is clearly wrong (it doesn't work when I try it!). How do I reconcile what the manual says about the macro being set to \relax when the \ifx<macro>\relax will not test correctly? Why would I know that I should resort to testing against \@empty? – A.Ellett Jul 23 '13 at 0:21
  • Oh. Maybe I know the answer to my latest question: it's because pgfkeys is doing something like \def<macro>{\relax} and \@empty is defined as \def\@empty{\relax}. So the meaning of the two macros is the same. Where as the meaning of \relax is not the meaning of \<macro> when defined to be \relax. Right? – A.Ellett Jul 23 '13 at 0:26
  • @A.Ellett \@empty means literally empty: \def\@empty{}. And this also true for the value-key (which is \defed into the special pgfk@/… macro). The \pgfkeysgetvalue macro \lets the second argument to the \pgfk@/… macro, basically \let\@epi@title\pgfk@/ae/…/title. Thus \@epi@title is as it would have been defined as \def\@epi@title{} and can now compared to \@empty. That has nothing to do with \relax. – Qrrbrbirlbel Jul 23 '13 at 0:34
  • So, what is the pgf manual talking about when it says that the macro will be equal to \relax? [see page 786 of the manual---I have the 2.10 CVS version.] – A.Ellett Jul 23 '13 at 0:43
  • @A.Ellett If the key has not been set excludes setting the key with .initial. There is no value-key that is not set. (This is just TeX: \csnameing an undefined macro name expands to \relax.) If the key has not been set by the user it simply has the value of its .initialization. If the user sets a key that has not been .initialized they will get an error message. – Qrrbrbirlbel Jul 23 '13 at 0:48
5

I don't know about PGF keys, but my idea would be to test whether the values are empty or not:

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\attrib}{m}{%
  \nopagebreak{\par\raggedleft\footnotesize #1\par}}

\ExplSyntaxOn
\keys_define:nn { ae/epigraph }
 {
  title .tl_set:N = \l_ae_epigraph_title_tl,
  title .initial:n = {},
  description .tl_set:N = \l_ae_epigraph_description_tl,
  description .initial:n = {},
  author .tl_set:N = \l_ae_epigraph_author_tl,
  author .initial:n = {},
  longestline .code:n = \ae_settowidth:n { #1 },
  afterskip .skip_set:N = \l_ae_epigraph_afterskip_skip,
  afterskip .initial:n = 1\baselineskip,
 }
\dim_new:N \l_ae_epigraph_longestline_dim
\dim_set:Nn \l_ae_epigraph_longestline_dim { 3in }

\cs_new_protected:Npn \ae_settowidth:n #1
 {
  \hbox_set:Nn \l_tmpa_box { #1 }
  \dim_set:Nn \l_ae_epigraph_longestline_dim { \box_wd:N \l_tmpa_box }
 }


\NewDocumentEnvironment{epigraph}{O{}}
  {
   \keys_set:nn { ae/epigraph } { #1 }
   \par\hspace*{\fill}
   \begin{minipage}[t]{\dim_eval:n { \l_ae_epigraph_longestline_dim+1em } }
  }
  {
   \par
   \group_begin:
   \raggedleft\footnotesize
   \tl_if_empty:NF \l_ae_epigraph_title_tl
    { \l_ae_epigraph_title_tl \\ }
   \tl_if_empty:NF \l_ae_epigraph_description_tl
    { \l_ae_epigraph_description_tl \\ }
   \tl_if_empty:NF \l_ae_epigraph_author_tl
    { \l_ae_epigraph_author_tl \\ }
   \vspace{-\prevdepth}
   \group_end:
   \vspace{1ex}
   \hrule
   \end{minipage}%
   \hspace*{\fill}%
   \vspace{\l_ae_epigraph_afterskip_skip}
  }
\ExplSyntaxOff

\pagestyle{empty}
\begin{document}

\begin{epigraph}[
  title={title},
  description={description},
  author={me},
  longestline=abcdefghijklm
]
  Scenario 1
\end{epigraph}

\begin{epigraph}[
  author={me},
  longestline=abcdefghijklm
]
  Scenario 2
\end{epigraph}

\begin{epigraph}[
  title={title},
  description={description},
  longestline=abcdefghijklm
]
  Scenario 3
\end{epigraph}

\begin{epigraph}[
  title={title},
  description={description},
  longestline=abcdefghijklm
]
  Scenario 4
\end{epigraph}

\begin{epigraph}[
  title={title},
  longestline=abcdefghijklm
]
  Scenario 5
\end{epigraph}

\begin{epigraph}[
  description={description},
  longestline=abcdefghijklm
]
  Scenario 6
\end{epigraph}

\end{document}

enter image description here

  • I like your solution (as usual). But, I don't understand how you're averting the application of \` when there's nothing else in the *list*. I'm guessing that it has something to do with \vspace{-\prevdepth}`, but I don't understand the mechanics of why this would work. Could you elaborate? – A.Ellett Jul 22 '13 at 20:08
  • The \\ in \raggedleft issues \par; nothing special. The \vspace{-\prevdepth} is to ensure constant distance from the last baseline to the rule. Yes, the second On should have been Off – egreg Jul 22 '13 at 20:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.