Measuring List Items in Tasks and Making Changes Accordingly

I made the following list for MCQs using taskspackage.

The problem is I need a command that can measure the contents of all the \tasks per question and determine the maximum width among them. And in case it is larger than a certain width (~200pt), it should change \task to \task* throughout the choices in the current question to give the output at the bottom.

\documentclass{article}

\usepackage[papersize={600pt,800pt},top=50pt,left=50pt,right=50pt,bottom=50pt]{geometry}
\usepackage{tikz}
\usepackage{calc}
\usepackage{enumitem}
\usepackage{tasks}

\makeatletter
\renewcommand\normalsize{\@setfontsize\normalsize{13.5pt}{17pt}}
\normalsize
\makeatother

\definecolor{ChoiceGray}{RGB}{204,204,204}

\newcommand*\Choice[1]{%
\begin{tikzpicture}[baseline=-5pt]
\node [gray,minimum width=22pt,minimum height=22pt,draw=ChoiceGray,rounded corners] {\large #1};
\end{tikzpicture}}

\newcounter{ChoiceLabel}
\newcommand*\ChoiceLabel{%
\refstepcounter{ChoiceLabel}%
\hskip1em\llap{\Choice{\Alph{ChoiceLabel}}}}%

\newcommand*{\MCQ}[1]{\setcounter{ChoiceLabel}{0}\begin{tasks}[after-item-skip=3mm,label=\ChoiceLabel](2)
#1
\end{tasks}}

\begin{document}
\begin{enumerate}[label=\arabic*.,leftmargin=1cm]

\item{Question text here.
\MCQ{
\task Some choice
\task Another choice
\task A choice with larger width that will break on two lines
\task The last one
}
}

\end{enumerate}
\end{document}


\documentclass{article}

\usepackage[papersize={600pt,800pt},top=50pt,left=50pt,right=50pt,bottom=50pt]{geometry}
\usepackage{tikz}
\usepackage{calc}
\usepackage{enumitem}
\usepackage{tasks}

\makeatletter
\renewcommand\normalsize{\@setfontsize\normalsize{13.5pt}{17pt}}
\normalsize
\makeatother

\definecolor{ChoiceGray}{RGB}{204,204,204}

\newcommand*\Choice[1]{%
\begin{tikzpicture}[baseline=-5pt]
\node [gray,minimum width=22pt,minimum height=22pt,draw=ChoiceGray,rounded corners] {\large #1};
\end{tikzpicture}}

\newcounter{ChoiceLabel}
\newcommand*\ChoiceLabel{%
\refstepcounter{ChoiceLabel}%
\hskip1em\llap{\Choice{\Alph{ChoiceLabel}}}}%

\newcommand*{\MCQ}[1]{\setcounter{ChoiceLabel}{0}\begin{tasks}[after-item-skip=3mm,label=\ChoiceLabel](2)
#1
\end{tasks}}

\begin{document}
\begin{enumerate}[label=\arabic*.,leftmargin=1cm]

\item{Question text here.
\MCQ{
\task* Some choice
\task* Another choice
\task* A choice with larger width that will break on two lines
\task* The last one
}
}

\end{enumerate}
\end{document}


Is it possible?

2 Answers

Here I use listofitems to initially parse the argument of \MCQ with \task as a separator. It loops through the parsed \tasklist measuring the longest one. If that longest one exceeds the threshold, here 200pt, it builds a token list that substitutes \task* wherever there had been \task in the original argument.

The original syntax of the OP is preserved.

\documentclass{article}

\usepackage[papersize={600pt,800pt},top=50pt,left=50pt,right=50pt,bottom=50pt]{geometry}
\usepackage{tikz}
\usepackage{calc}
\usepackage{enumitem}
\usepackage{tasks}

\makeatletter
\renewcommand\normalsize{\@setfontsize\normalsize{13.5pt}{17pt}}
\normalsize
\makeatother

\definecolor{ChoiceGray}{RGB}{204,204,204}

\newcommand*\Choice[1]{%
\begin{tikzpicture}[baseline=-5pt]
\node [gray,minimum width=22pt,minimum height=22pt,draw=ChoiceGray,rounded corners] {\large #1};
\end{tikzpicture}}

\newcounter{ChoiceLabel}
\newcommand*\ChoiceLabel{%
\refstepcounter{ChoiceLabel}%
\hskip1em\llap{\Choice{\Alph{ChoiceLabel}}}}%

\newtoks\tasktoks
\usepackage{listofitems}
\newcommand*{\MCQ}[1]{%
\setsepchar{\task}%
\ignoreemptyitems%
\readlist*\tasklist{#1}%
\def\longtask{0pt}%
\foreachitem\taskitem\in\tasklist{% MEASURE THE LONGEST TASK
\setbox0=\hbox{\taskitem}%
\ifdim\wd0>\longtask\relax\edef\longtask{\the\wd0}\fi%
}%
\setcounter{ChoiceLabel}{0}%
\ifdim\longtask>200pt\relax% FOR LONG TASKS, EMPLOY \task*
\foreachitem\taskitem\in\tasklist{%
\tasktoks\expandafter{\the\tasktoks\task*}%
\tasktoks\expandafter\expandafter\expandafter{%
\expandafter\the\expandafter\tasktoks\taskitem}%
}%
\else%
\tasktoks{#1}%
\fi%
\def\tmp{ \begin{tasks}[after-item-skip=3mm,label=\ChoiceLabel](2)}%
\expandafter\tmp\the\tasktoks%
\end{tasks}%
}
\begin{document}
\begin{enumerate}[label=\arabic*.,leftmargin=1cm]
\item{Question text here.
\MCQ{
\task Some choice
\task Another choice
\task Third choice
\task The last one
}
}
\end{enumerate}
\begin{enumerate}[label=\arabic*.,leftmargin=1cm]
\item{Question text here.
\MCQ{
\task Some choice
\task Another choice
\task A choice with larger width that will break on two lines
\task The last one
}
}
\end{enumerate}
\end{document}


If you a can accept a (slightly) modified input of the task list like :

\MCQ{Some choice\\
Another choice\\
A choice with a larger width that will break on two lines\\
The last one
}


below is a working solution. It uses parsing and iteration from package listofitems and a few functions of etoolbox package. Th key idea is to first iterate to find the maximl width, an secondly to iterate to build the body of the tasks environment. This is made rather elaborated because of the very special handling of tasks content (I used for that the answer from clemens for the question tasks-environment-vs-pgffors-foreach). If you would use a more standard environment, everything can be codded in four or five lines.

\documentclass{article}

\usepackage[papersize={600pt,800pt},top=50pt,left=50pt,right=50pt,bottom=50pt]{geometry}
\usepackage{tikz}
\usepackage{calc}
\usepackage{enumitem}
\usepackage{tasks}
\usepackage{listofitems}
\usepackage{etoolbox}

\makeatletter
\renewcommand\normalsize{\@setfontsize\normalsize{13.5pt}{17pt}}
\normalsize
\makeatother

\definecolor{ChoiceGray}{RGB}{204,204,204}

\newcommand*\Choice[1]{%
\begin{tikzpicture}[baseline=-5pt]
\node [gray,minimum width=22pt,minimum height=22pt,draw=ChoiceGray,rounded corners] {\large #1};
\end{tikzpicture}}

\newcounter{ChoiceLabel}
\newcommand*\ChoiceLabel{%
\refstepcounter{ChoiceLabel}%
\hskip1em\llap{\Choice{\Alph{ChoiceLabel}}}%
}
%------------------------
\newcommand\switchtwo[2]{#2#1}
\newcommand\expandsecond[2]{\expandafter\switchtwo\expandafter{#1}{#2}}
\newlength{\tsklenmax}
\newcommand{\setTaskLenMax}[1]{\setlength{\tsklenmax}{#1}}
\newcommand{\maxdim}[2]{\ifdimgreater{#1}{#2}{#1}{#2}}

\newcommand*{\MCQ}[1]{
% initize
\ifdef{\thetasks}{\let\thetasks\undefined}{}
\newlength{\tsklen}\newlength{\tsklentempmax}
\setlength{\tsklentempmax}{\tsklenmax}
% read tasks
\setsepchar{\\} \ignoreemptyitems
\greadlist*\thetasks{#1}
% find maximal width
\foreachitem\atask\in\thetasks{
\settowidth{\tsklen}{\atask}
\setlength{\tsklentempmax}{\maxdim{\tsklen}{\tsklentempmax}}
}
% choose task version
\ifdimgreater{\tsklentempmax}{\tsklenmax}{\def\usestar{\noexpand\task*}}{\def\usestar{\noexpand\task}}
% populate task list
\foreachitem\atask\in\thetasks{
\begingroup
\edef\x{\endgroup
\expandafter\noexpand\csname g@addto@macro\endcsname
\noexpand\taskslist{\usestar \atask}%
}\x
}
% display tasks
\setcounter{ChoiceLabel}{0}%
\expandsecond\taskslist
{\begin{tasks}[after-item-skip=3mm,label=\ChoiceLabel](2)}
\end{tasks}
}

\begin{document}

\setTaskLenMax{250pt}

\begin{enumerate}
\item question 1
\MCQ{Some choice\\
Another choice\\
A choice with a larger width that will break on two lines\\
The last one
}
\item Question 2
\MCQ{Some choice\\
Another choice\\
With reduced larger width \\
The last one
}
\end{enumerate}

\end{document}


Resulting in:

The new MCQ command and its auxillary functions/length are defined at the end of the preamble.

Obviously, as you already load tikz to create the fancy labels, you could replace list handling and iteration by those of pgf.

• Ha. I made my solution without looking at yours. I upvote because of their extreme similarity (though I did use tokens rather than \g@addto@macro). In addition, and importantly, I did preserve the syntax of the OP. – Steven B. Segletes Mar 23 '18 at 3:05