3

My question is about how to improve an already working macro via the keycommand package. The small inclusion shows my efforts so far:

\documentclass{article}
\usepackage[margin=0.95in]{geometry}
\usepackage[draft,columns=1]{typogrid}
\usepackage{varwidth}
\usepackage{xkeyval}
\usepackage{keycommand}
% patch by Joseph Wright ("bug in the definition of \ifcommandkey (2010/04/27 v3.1415)"),
% https://tex.stackexchange.com/a/35794/
\begingroup
  \makeatletter
  \catcode`\/=8 %
  \@firstofone
    {
      \endgroup
      \renewcommand{\ifcommandkey}[1]{%
        \csname @\expandafter \expandafter \expandafter
        \expandafter \expandafter \expandafter \expandafter
        \kcmd@nbk \commandkey {#1}//{first}{second}//oftwo\endcsname
      }
    }
%=======================%
\usepackage{xcolor}
\usepackage{tikz}
\usetikzlibrary{positioning}

\definecolor{tikzgray}{HTML}{E5E5FF} \definecolor{tikzyellow}{HTML}{F4F4CC}

\makeatletter \newkeycommand\TikzBoxx[ bool draft=false, rule=.5in, width=.811\textwidth, text={no text here}, scolor=tikzyellow, lcolor=tikzgray] [1]{% \ifcommandkey{\commandkey{draft}} {\begin{tikzpicture}[node distance = 0mm and 2mm,box/.style={rectangle,draw}]} {\begin{tikzpicture}[node distance = 0mm and 2mm,box/.style={rectangle}]} \rule{\commandkey{rule}}{0pt}% \node (n1) [box, fill=\commandkey{scolor}] {\commandkey{text}};% \node (n2) [box, fill=\commandkey{lcolor}, text width=\commandkey{width}, below right=of n1.north east] {% \vbox{\noindent\ #1} };% \end{tikzpicture} } \makeatother

\def \HELLO {Hello World!} \def \LTEXT { Each key (may) store some \emph{tokens} and there exist\newline% commands, described below, for setting, getting, and\newline% changing the tokens stored in a key. However, you will\newline% only very seldom use these commands directly.% }

\begin{document}

\vspace{\baselineskip} \TikzBox[text=\HELLO]{\LTEXT}

\end{document}

It works, but to my mind, it is also ugly. The problem is in the brute force way I make what should be a simple text substitution using:

    \ifcommandkey{\commandkey{draft}}
        {\begin{tikzpicture}[node distance = 0mm and 2mm,box/.style={rectangle,draw}]}
        {\begin{tikzpicture}[node distance = 0mm and 2mm,box/.style={rectangle}]}

I initially tried building two \def's, but that attempt erred out in the latexmake run. The idea I tried was to replace the hardcoded value in the .style={...} with .style={\Style} which produces the following:

! Package pgfkeys Error: I do not know the key '/tikz/rectangle,draw' and I am
going to ignore it. Perhaps you misspelled it.

After a variety of work-around attempts, I changed my approach to the code as shown.

Even though the working code only duplicates one line, I'd prefer a solution with some form of text substitution. At this point, I lack the experience needed to have a clue! All clues gratefully accepted.

egreg
  • 1,121,712
hsmyers
  • 1,507
  • 1
    If you load pgf why don't you just use it to make the command more elegant? That is, why do you use draft from another key management system to append a key to a pgf style? –  Dec 02 '20 at 22:47
  • @TikZling I'm shopping for a kv approach that I like and can adopt for macro development in general. I've not climbed the list to pgf as yet—might now! – hsmyers Dec 03 '20 at 04:05
  • Note that your code in your MWE has quite a few stray spaces, e.g., at the start of \LTEXT, after \end{tikzpicture}, after \vbox{\noindent\ #1}, etc. – Skillmon Dec 03 '20 at 10:35
  • I tend to take care of stray spaces somewhat later in the process. I'm still in the "Make it work" phase. The corollary that says "Write code that you like!" – hsmyers Dec 03 '20 at 15:46
  • 1
    @hsmyers I just wanted to make sure that you're aware of those. And while I don't want to say you how you should code, it might be a good idea to not let them slip in in the first place, as it is a lot easier to just put a % while you write the code and your mind is at that position anyways, than to later search for all the possible stray spaces. At least from my experience... – Skillmon Dec 04 '20 at 07:54

3 Answers3

4

This is not really a sophisticated solution but merely to point out that you do not need to add a second key val system if you already load pgf.

\documentclass{article}
\usepackage[margin=0.95in]{geometry}
\usepackage[draft,columns=1]{typogrid}
\usepackage{varwidth}
\usepackage{tikz}
\usetikzlibrary{positioning}

\definecolor{tikzgray}{HTML}{E5E5FF} \definecolor{tikzyellow}{HTML}{F4F4CC}

\makeatletter \newif\iftikzbox@draft \tikzset{tikzbox/.cd,draft/.is if=tikzbox@draft, draft/.default=true, rule/.initial=0.5in, width/.initial=.811\textwidth, text/.initial={no text here}, scolor/.initial=tikzyellow, lcolor/.initial=tikzgray} \newcommand\TikzBox[2][]{\begingroup\tikzset{tikzbox/.cd,#1}% \def\commandkey##1{\pgfkeysvalueof{/tikz/tikzbox/##1}}% %\rule{\commandkey{rule}}{0pt}%<- what is this good for? \iftikzbox@draft \begin{tikzpicture}[node distance = 0mm and 2mm,box/.style={rectangle,draw}] \else \begin{tikzpicture}[node distance = 0mm and 2mm,box/.style={rectangle}] \fi \node (n1) [box, fill/.expanded=\commandkey{scolor}] {\commandkey{text}};% \node (n2) [box, fill/.expanded=\commandkey{lcolor}, text width=\commandkey{width}, below right=of n1.north east] {% \vbox{\noindent\ #2} };% \end{tikzpicture}\endgroup } \makeatother

\def \HELLO {Hello World!} \def \LTEXT { Each key (may) store some \emph{tokens} and there exist\newline% commands, described below, for setting, getting, and\newline% changing the tokens stored in a key. However, you will\newline% only very seldom use these commands directly.% }

\begin{document}

\vspace{\baselineskip} \TikzBox[text=\HELLO]{\LTEXT}

\TikzBox[draft,text=\HELLO]{\LTEXT}

\end{document}

enter image description here

As I said, many details can be improved here. This is just to illustrated the principle.

  • The \rule is for horizontal placement. For the intended work, the indent is fairly large and the rule gives me an easy solution. – hsmyers Dec 03 '20 at 04:09
  • 1
    @hsmyers OK. In the above code, when kept, it produces dimension too large errors. You can simply work with the \path command to add horizontal space. Or just put a \hfill before the tikzpicture. –  Dec 03 '20 at 04:13
  • After thinking about it, since posting, I think a better question might have been "Is there a way to create a variable (text) and if yes how do you use it?" If I do a \def\Style{rectangle, draw} how do I expand it in the .style portion of the begin{Tikzpicture} environment? – hsmyers Dec 03 '20 at 04:14
  • 1
    @hsmyers \begin{tikzpicture}[style/.expanded=\Style]. Of course, you can just define a style instead of a macro, \tikzset{mystyle/.style={rectangle, draw}}} and \begin{tikzpicture}[mystyle]. –  Dec 03 '20 at 04:16
  • The positioning goal was such so the gap between boxes is two or three characters wide and then such that the second box runs all the way to the text margin on the right side. I'll look at both \path and \hfill. – hsmyers Dec 03 '20 at 04:18
  • 1
    @hsmyers You can use below right=2em of n1.north east or another distance instead of 2em, and use \hfill before \begin{tikzpicture}. –  Dec 03 '20 at 04:20
  • My lack of knowledge about Tikz is showing. I've even got the manual printed out (in two volumes.) That sadly is a 2.x version and like me is behind the times! – hsmyers Dec 03 '20 at 04:25
4

Edit: Starting with version 1.0 of expkv-cs the behaviour of \ekvcValueSplit was slightly changed, it now doesn't forward the key list as well as the value anymore, just the value is forwarded. The second example was adapted accordingly.


Using expkv-cs instead of keycommand, you can create the key=value interface in a way that the values to the keys are passed in as normal parameters so that you don't have to handle the expansion of \commandkey and friends.

The predefined possibilities of expkv-cs are limited, as the mechanism works fully expandable, but I think for this usecase it provides everything you need.

\documentclass{article}
\usepackage[margin=0.95in]{geometry}
\usepackage[draft,columns=1]{typogrid}
\usepackage{expkv-cs}
\usepackage{tikz}

\definecolor{tikzgray}{HTML}{E5E5FF} \definecolor{tikzyellow}{HTML}{F4F4CC}

\makeatletter % \TikzBox@kv will split the key=value list into normal arguments and pass them % to \TikzBox@do \ekvcSplitAndForward\TikzBox@kv\TikzBox@do { style = {rectangle,draw} % #1 ,width = .811\textwidth % #2 ,text = no text here % #3 ,scolor = tikzyellow % #4 ,lcolor = tikzgray % #5 ,pre = \hfill % #6 ,sep = 2mm % #7 } % We can define other keys that call the existing keys with predefined values. % The nmeta type will be a key that doesn't take a value (and will throw an % error if used with a value) and is equivalent to the keys you specify here. \ekvcSecondaryKeys\TikzBox@kv { nmeta draft = {style=rectangle} ,nmeta nofill = {pre=} ,nmeta blank = {draft,scolor=white,lcolor=white} } % The user macro will search for the optional argument. \newcommand\TikzBox[1][]{\TikzBox@kv{#1}} % \TikzBox@do carries out the actual output. It gets the 7 key-values from % \TikzBox@kv and grabs an additional argument, which is the mandatory argument % for \TikzBox from the user's point of view. \newcommand\TikzBox@do[8] {% \leavevmode #6% \begin{tikzpicture}[box/.style={#1}] \node (n1) [box, fill=#4] {#3};% \node (n2) at (n1.north east) [anchor=north west, xshift=#7, box, fill=#5, text width=#2] {\ #8};% \end{tikzpicture}% } \makeatother

\def \HELLO {Hello World!} \def \LTEXT {% Each key (may) store some \emph{tokens} and there exist\newline% commands, described below, for setting, getting, and\newline% changing the tokens stored in a key. However, you will\newline% only very seldom use these commands directly.% }

\begin{document}

\TikzBox[text=\HELLO]{\LTEXT}\par \TikzBox[text=\HELLO,draft]{\LTEXT}\par \TikzBox[text=\HELLO,blank]{\LTEXT}\par \TikzBox[width=.5\textwidth,sep=2cm,lcolor=blue]{\LTEXT}\par \TikzBox[style={circle,draw},width=.55\textwidth,nofill]{\LTEXT}\par

\end{document}


One of the limitations almost becomes aparent: The output macro grabs 8 parameters, so there is only room for one additional key in that implementation. expkv-cs provides a second mechanism that scales better for more keys (while still being fully expandable), by putting all the values in one parameter and providing means to access individual values from that list. This approach works good when you don't need direct access to a value, but you can split of individual values from that list before calling other macros.

\documentclass{article}
\usepackage[margin=0.95in]{geometry}
\usepackage[draft,columns=1]{typogrid}
\usepackage{expkv-cs}
\usepackage{tikz}

\definecolor{tikzgray}{HTML}{E5E5FF} \definecolor{tikzyellow}{HTML}{F4F4CC}

\makeatletter % \TikzBox@kv will parse the key=value list and put it into one single argument % in a way that it is easy to access individual values. The style key has to be % split of beforehand, as we need its value directly so that pgfkeys parses it % correctly. \ekvcHash\TikzBox@kv { style = {rectangle,draw} ,width = .811\textwidth ,text = no text here ,scolor = tikzyellow ,lcolor = tikzgray ,pre = \hfill ,sep = 2mm } {\ekvcValueSplit{style}{#1}{\TikzBox@do{#1}}} % We can define other keys that call the existing keys with predefined values. % The nmeta type will be a key that doesn't take a value (and will throw an % error if used with a value) and is equivalent to the keys you specify here. \ekvcSecondaryKeys\TikzBox@kv { nmeta draft = {style=rectangle} ,nmeta nofill = {pre=} ,nmeta blank = {draft,scolor=white,lcolor=white} } % The user macro will search for the optional argument. \newcommand\TikzBox[1][]{\TikzBox@kv{#1}} % \TikzBox@do carries out the actual output. It gets the key-values as a list % from \TikzBox@kv as well as the extracted style key, and grabs an additional % argument, which is the mandatory argument for \TikzBox from the user's point % of view. \newcommand\TikzBox@do[3] {% \leavevmode \ekvcValue{pre}{#1}% \begin{tikzpicture}[box/.style={#2}] \node (n1) [box, fill=\ekvcValue{scolor}{#1}] {\ekvcValue{text}{#1}};% \node (n2) at (n1.north east) [ anchor=north west ,xshift=\ekvcValue{sep}{#1} ,box ,fill=\ekvcValue{lcolor}{#1} ,text width=\ekvcValue{width}{#1} ] {\ #3};% \end{tikzpicture}% } \makeatother

\def \HELLO {Hello World!} \def \LTEXT {% Each key (may) store some \emph{tokens} and there exist\newline% commands, described below, for setting, getting, and\newline% changing the tokens stored in a key. However, you will\newline% only very seldom use these commands directly.% }

\begin{document}

\TikzBox[text=\HELLO]{\LTEXT}\par \TikzBox[text=\HELLO,draft]{\LTEXT}\par \TikzBox[text=\HELLO,blank]{\LTEXT}\par \TikzBox[width=.5\textwidth,sep=2cm,lcolor=blue]{\LTEXT}\par \TikzBox[style={circle,draw},width=.55\textwidth,nofill]{\LTEXT}\par

\end{document}


Both examples give identical results:

enter image description here

Skillmon
  • 60,462
4

I'd suggest expl3 style key-value. The only nuisance is using ~ for spaces in TikZ keys. Avoid keycommand: it's buggy and unmaintained.

\documentclass{article}
\usepackage[margin=0.95in]{geometry}
\usepackage[draft,columns=1]{typogrid}
\usepackage{varwidth}
\usepackage{xcolor}
\usepackage{tikz}
\usetikzlibrary{positioning}

\definecolor{tikzgray}{HTML}{E5E5FF} \definecolor{tikzyellow}{HTML}{F4F4CC}

\ExplSyntaxOn

\NewDocumentCommand{\TikzBoxx}{O{}m} { \keys_set:nn { myers/boxx } { draft=false, rule=.5in, width=.811\textwidth, text={no~text~here}, scolor=tikzyellow, lcolor=tikzgray, #1 } \myers_boxx:n { #2 } }

\keys_define:nn { myers/boxx } { draft .bool_set:N = \l__myers_boxx_draft_bool, draft .default:n = true, rule .dim_set:N = \l__myers_boxx_rule_dim, width .dim_set:N = \l__myers_boxx_width_dim, text .tl_set:N = \l__myers_boxx_text_tl, scolor .tl_set:N = \l__myers_boxx_scolor_tl, lcolor .tl_set:N = \l__myers_boxx_lcolor_tl, }

\cs_new_protected:Nn \myers_boxx:n { \bool_if:NTF \l__myers_boxx_draft_bool { \begin{tikzpicture}[node~distance = 0mm~and~2mm,box/.style={rectangle,draw}] } { \begin{tikzpicture}[node~distance = 0mm~and~2mm,box/.style={rectangle}] } \rule{\l__myers_boxx_rule_dim}{0pt} \node~(n1) [box, fill=\l__myers_boxx_scolor_tl] {\l__myers_boxx_text_tl}; \node (n2) [ box, fill=\l__myers_boxx_lcolor_tl, text~width=\l__myers_boxx_width_dim, below~right=of~n1.north~east ] { \begin{varwidth}{\l__myers_boxx_width_dim} #1 \end{varwidth} }; \end{tikzpicture} } \ExplSyntaxOff

\newcommand\HELLO {Hello World!} \newcommand\LTEXT {% Each key (may) store some \emph{tokens} and there exist\newline commands, described below, for setting, getting, and\newline changing the tokens stored in a key. However, you will\newline only very seldom use these commands directly.% }

\begin{document}

\TikzBoxx[text=\HELLO]{\LTEXT}

\TikzBoxx[text=\HELLO,scolor=red!20,lcolor=green!30,rule=4pt]{\LTEXT}

\TikzBoxx[draft,text=\HELLO,scolor=red!20,lcolor=green!30,rule=4pt]{\LTEXT}

\end{document}

enter image description here

egreg
  • 1,121,712
  • Why not set the keys globally instead and wrap your definition of \TikzBoxx in \group_begin: ... \group_end:? – Gaussler Dec 03 '20 at 12:51
  • @Gaussler That's not always a good choice: grouping could affect other macros; but it's definitely a possibility. – egreg Dec 03 '20 at 12:57
  • Well, for one thing, as you increase the number of keys, the solution you are using will become slower. – Gaussler Dec 03 '20 at 12:59
  • What are the naming conventions? I.E., where did the l_myers come from? – hsmyers Dec 03 '20 at 15:53
  • @egreg What other bugs are in keycommand? I've patched the one I was aware of with the code from Joseph Wright. – hsmyers Dec 03 '20 at 15:55
  • @hsmyers the naming conventions on variables in expl3 are: local variables start with l, global ones with g, and constants with c. The next part of the name is the module, in this case, the module is named myers (your name), this way clashes with other modules are ruled out. If the scope-prefix (global/local/constant) is separated by two underscores the variable is private, one underscore means it can be used by other modules. After the module follows the description (e.g., width), and after that the variable type (e.g., dim for dimension, or tl for token list). – Skillmon Dec 03 '20 at 16:31
  • @hsmyers naming conventions on functions are similar: two leading underscores mean it's a private-function, no leading underscore means it's public and can be used by other modules. Then follows the module name (again myers), after that the description (e.g., boxx). After the description follows a colon and after the colon the argument-specification this macro will take. n means a normal argument, N means a single token (other argument types deal with expansion). So \myers_boxx:n is a macro of module myers, it does boxx, and it reads one normal argument. – Skillmon Dec 03 '20 at 16:34