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.

  • 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? – user229669 Dec 2 '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 3 '20 at 4: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 3 '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 3 '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 4 '20 at 7:54
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 3 '20 at 4: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. – user229669 Dec 3 '20 at 4: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 3 '20 at 4: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]. – user229669 Dec 3 '20 at 4:16
  • 1
    @hsmyers You can use below right=2em of n1.north east or another distance instead of 2em, and use \hfill before \begin{tikzpicture}. – user229669 Dec 3 '20 at 4:20
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

  • Why not set the keys globally instead and wrap your definition of \TikzBoxx in \group_begin: ... \group_end:? – Gaussler Dec 3 '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 3 '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 3 '20 at 12:59
  • What are the naming conventions? I.E., where did the l_myers come from? – hsmyers Dec 3 '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 3 '20 at 15:55
4

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}
% 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

  • I included the \hfill to move the contents to the right margin, as suggested by TikZling. – Skillmon Dec 5 '20 at 9:29

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.