2

I have a macro that makes a \cline of user-specified thickness. I want to create a * and un-* version to account for the difference between two cases depending on whether there is more than one \cline in a sequence (e.g., \cline{1-2}\cline{5-6}). (When the \cline rules are thick, they can clobber part/all of content of the row beneath unless there's a correcting \vskip at the end.)

The unstarred version adds this \vskip at the end, and you'd use it for the last \cline of a sequence (which subsumes the case where it's the only \cline). The starred version omits this \vskip, and you'd use it for all but the final \cline in a sequence. In the below code, \ClineTStar is that starred macro without the \vskip correction. I then define \ClineTNoStarto simply call \ClineTStar and add the correcting \vskip at the end.

These differently named versions of the macro are working fine, when I call them with their distinct names. See the output below. The starred \clines after the row starting with A & … undesirably clobber the row underneath (that should start with I & J & K…). But the \clines after row Q & R… (which is two starred followed by one unstarred macro) properly leave room for the next row's content Y & Z…

But I'd like to call this pair of macros as \ClineT* and \ClineT. When I follow the traditional \@ifstar approach to create these flavors, and call either version, I get an error message like:

Misplaced \omit. \@ClineTStar #1-#2#3\@nil ->\omit \@multicnt #1\advance \@multispan \m@ne \i... l.36 \ClineT*{1-2}{10pt} \ClineT*{4-5}{10pt} \ClineT{7-8}{10pt}

I've commented out the line \ClineT*{1-2}{10pt} \ClineT*{4-5}{10pt} \ClineT{7-8}{10pt} so that it will compile.

What am I doing wrong? (I don't want to use LaTeX3 methods, because, AFAIK, that's not compatible with Overleaf v1. So I'd like to fix this \@ifstar approach.)

\documentclass{article}
\makeatletter
\def\ClineTStar#1#2{\@ClineTStar#1#2\@nil}
\def\@ClineTStar#1-#2#3\@nil{%
    \omit
    \@multicnt#1%
    \advance\@multispan\m@ne
    \ifnum\@multicnt=\@ne\@firstofone{&\omit}\fi
    \@multicnt#2%
    \advance\@multicnt-#1%
    \advance\@multispan\@ne
    \leaders\hrule\@height#3\hfill
    \cr
    \noalign{\vskip-#3}%
}
\makeatother
\newcommand{\ClineTNoStar}[2]{%
    \ClineTStar{#1}{#2}%
    \noalign{\vskip#2}%
}

\makeatletter
\newcommand{\ClineT}{\@ifstar{\ClineTStar}{\ClineTNoStar}}
\makeatother

\begin{document}
\begin{tabular}{| c | c | c | c | c | c | c | c |}
\hline
A & B & C & D & E & F & G & H \\
\ClineTStar{1-2}{10pt} \ClineTStar{4-5}{10pt} \ClineTStar{7-8}{10pt}
I & J & K & L & M & N & O & P \\
Q & R & S & T & U & V & W & X \\
\ClineTStar{1-2}{10pt} \ClineTStar{4-5}{10pt} \ClineTNoStar{7-8}{10pt}
Y & Z & A & B & C & D & E & F \\
%\ClineT*{1-2}{10pt} \ClineT*{4-5}{10pt} \ClineT{7-8}{10pt}
E & F & G & H & I & J & K & L \\
\hline
\end{tabular}
\end{document}

enter image description here

  • Unrelated to your question: I don’t think your implementation will work well with \ClineTStar{9-11}{10pt}. – Ruixi Zhang Sep 4 '18 at 1:41
  • @RuixiZhang, it doesn't like the corresponding \cline{9-11} either. I'm happy if this works in the cases that \cline accepts. – Jim Ratliff Sep 4 '18 at 2:58
  • The original \@cline has two markers: Everything that comes before - is #1; everything after - but before \@nil is #2. It breaks down in your code since #2 can only be one token. – Ruixi Zhang Sep 4 '18 at 3:05
  • @RuixiZhang, what I was referring to is that {9-11} means "span from column 9 through column 11." In my example, there are only 8 columns. Neither my macro nor \cline works gracefully here when you specify an out-of-bounds column range. Mine seems to work fine for column ranges that are feasible. – Jim Ratliff Sep 4 '18 at 3:32
  • I see. I was referring to the situation when you have 10 or more columns. This implementation is an equivalence of a ticking time bomb. In the case of an 11-column table with \ClineTStar{9-11}{10pt}, your \@ClineTStar would grab 9 as #1, 1 as #2 and 110pt as #3. – Ruixi Zhang Sep 4 '18 at 4:00
4

The problem here is that \omit really must be the first command that appears in the tabular cell after \\. If you use macros there, they have to be fully expandable and \omit has to be the very first token of that expansion. As \@ifstar uses several assignments before it can make the decision if the next token is *, it will fail here. So we have to replace \ClineT by a fully expandable macro:

\documentclass{article}
\makeatletter
\def\ClineTStar#1#2{\@ClineTStar#1#2\@nil}
\def\@ClineTStar#1-#2#3\@nil{%
    \omit
    \@multicnt#1%
    \advance\@multispan\m@ne
    \ifnum\@multicnt=\@ne\@firstofone{&\omit}\fi
    \@multicnt#2%
    \advance\@multicnt-#1%
    \advance\@multispan\@ne
    \leaders\hrule\@height#3\hfill
    \cr
    \noalign{\vskip-#3}%
}
\makeatother
\newcommand{\ClineTNoStar}[2]{%
    \ClineTStar{#1}{#2}%
    \noalign{\vskip#2}%
}

\makeatletter
\def\ClineT#1{%
    \expandafter\ifx\@firstofmany#1\relax\@end*%
        \expandafter\ClineTStar
    \else
        \ClineT@#1%
    \fi
}
\def\ClineT@#1\fi{\fi\ClineTNoStar{#1}}
\def\@firstofmany#1#2\@end{#1}
\makeatother

\begin{document}
\begin{tabular}{| c | c | c | c | c | c | c | c |}
\hline
A & B & C & D & E & F & G & H \\
\ClineTStar{1-2}{10pt} \ClineTStar{4-5}{10pt} \ClineTStar{7-8}{10pt}
I & J & K & L & M & N & O & P \\
Q & R & S & T & U & V & W & X \\
\ClineTStar{1-2}{10pt} \ClineTStar{4-5}{10pt} \ClineTNoStar{7-8}{10pt}
Y & Z & A & B & C & D & E & F \\
\ClineT*{1-2}{10pt} \ClineT*{4-5}{10pt} \ClineT{7-8}{10pt}
E & F & G & H & I & J & K & L \\
\hline
\end{tabular}
\end{document}

enter image description here

\ClineT takes one argument that will be either * or a group like {1-2}. We find the first token of that argument using the helper macro \*firstofmany and compare it against *. If they are the same, \ClineTStar is the next thing to be called.

A bit trickier is the other branch because we need to re-apply the already read argument to \ClineTNoStar. Simply \else\ClineTNoStar{#1}\fi won't work—now \fi would become the second argument of \ClineTNoStar. The solution is to use a helper macro that uses \fi as delimited parameter, immediately puts it back to the token stream and calls the actual macro as the final part of the expansion.

  • 1
    A beautiful answer. +1 – Steven B. Segletes Sep 4 '18 at 2:48
  • That certainly fixes it! This is area of TeX knowledge that I know little about, so it's somewhat Greek to me, but I'm very happy to have the solution. – Jim Ratliff Sep 4 '18 at 2:50

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.