3

I am a teacher. When I write an exam, I would like to be able to print the student version of the exam and then print the exam answer key. My students write their answers directly on the exam, so the space I've left them to write their answer will typically be large enough to contain anything I want to have in the answer key. The problem is how to print the two versions of the exam.

Note that I am familiar with the EXAM package (https://ctan.org/pkg/exam?lang=en), and am not currently interested in using it. Looking through other answers, I think what I want is something based on smash (What does \smash do, and where is it documented?).

Shown below is an MWE that has two implementations of \answerkey. Version A is my attempt to do what I want -- optionally include the answer, do it in red, and smash it so that it doesn't take up any space. Unfortunately, for the first use below, the vertical spacing is still changing with and without the answers. For the second use below, it breaks totally because the answer has multiple paragraphs.

The optimal answer is a single \answerkey command that can handle both of the uses below. The goal is that the spacing of the document not change when \answerstrue or \answersfalse is used.

\documentclass[10pt]{article}

\usepackage{xcolor} \usepackage{tikz}

\newif\ifanswers \answerstrue %\answersfalse

\newcommand{\answerkeyA}[1]{% \ifanswers% \textcolor{red}{\smash{\textbf{\parbox[t]{\linewidth}{#1}}}}% \fi% }

\newcommand{\answerkeyB}[1]{% \ifanswers% #1% \fi% }

\begin{document}

\begin{enumerate} \item Answer the following question with words.

\answerkeyA{ I do not like them in a house. I do not like them with a mouse. I do not like them here or there. I do not like them anywhere. I do not like green eggs and ham. }

\vspace*{1in}

\item Draw a picture to illustrate the following problem.

\answerkeyB{ Answer:

\begin{tikzpicture}[scale=0.3,font=\sffamily] \draw [step=1.0, thin, gray!50] (-3, -3) grid (3, 3); \draw [arrows={latex-latex}] (-3,0) -- coordinate (x axis mid) (3,0); \draw [arrows={latex-latex}] (0,-3) -- coordinate (y axis mid) (0,3); \end{tikzpicture} }

\vspace*{2in}

\item The next problem should be here. \end{enumerate}

\end{document}

3 Answers3

3

You could do \smash followed by \vspace, but \parbox has parameters for controlling its height as well as width.

Here is what I would do, given the logical switch \ifanswers and specified space for the students to write answers.

\newcommand\answer[2]{% #1 = size  #2 = answer
  \par \noindent
  \parbox[t][#1][s]{\linewidth}{%
     \ifanswers \color{red}#2\fi
     \par\vfill
  }\par
  \setlength\prevdepth{0pt}\medskip
}

The [s] argument for "stretch" and the \vfill at the end of the box combine to be like a [t] argument, except with [t] there is no warning when the text can't fit in the allotted space. This version gives an overfull box warning when the answer text is too large, and the answer can print over the next question.

There is a little bit of extra space above the answer, which may be just what is needed. (It is caused by having the top edge of the answer be at the "baseline".) Also, there is optional \medskip extra space after the answer to balance.

With a little more, the \answer macro can be tightened to give regular baseline spacing between the question and the answer.

\newcommand\answer[2]{% #1 = size  #2 = answer
  \par \noindent
  \parbox[t][#1][s]{\linewidth}{%
     % the next line preserves the top baseline alignment
     \vskip-\ht\strutbox \noindent\strut
     \ifanswers \color{red}\ignorespaces#2\fi
     \par\vfill
  }\par
  \setlength\prevdepth{0pt}%
}

In that version the paragraph is started before the answer text, and a "strut" plus a negation is used to bring baseline alignment to where it belongs. The \ignorespaces allows an answer to be written like

\answer{3cm}{
   this is the answer...
}

without introducing spurious spaces.

  • I need to look at this more closely. It requires me to use the answer command to do the spacing in the student version of the exam, but I think that is workable. I need to understand more about what the options to the parbox are doing. – Christopher Donham Aug 23 '20 at 15:18
  • Two questions: 1) What is the purpose of the \ignorespaces in the proposed solution; 2) I'm seeing something slightly less than a baselineskip in difference between the versions with and without answers. I'm using the first of your proposed solutions to do this. Do you know where that difference in spacing is coming from? – Christopher Donham Aug 23 '20 at 15:30
  • The \ignorespaces is because the paragraph has already been started, and the answer text may well start on a line below \answer{3cm}{. The spacing error comes from parbox treating the size differently from how I realized! It is tied up with the attempt to keep baselines in registration (\noindent...\ignorespaces). Will improve answer. – Donald Arseneau Aug 24 '20 at 02:08
  • For my purposes, please see if you can improve the first solution you gave above. If the answer is too big for the space provided, I need to rewrite the answer to fit or reference something outside of the exam (e.g. "see separate solution guide"). I am trying to overlay the answers on the exam without changing the formatting of the exam. Its actually better for the next question to overlap the answer so that I can see that there is a problem, rather than changing the spacing, causing questions to shift onto other pages which I might not notice. – Christopher Donham Aug 24 '20 at 02:23
  • 1
    I just checked out the new version. It seems to work quite well. Thanks much for your time and effort. – Christopher Donham Aug 24 '20 at 03:34
2

If you want the spacing to not change, then you need to still do something with the text even in the student version, so that TeX knows what the spacing should be. \vphantom will leave the appropriate vertical spacing, but it also breaks with paragraphs. This solution is to write the answers anyway, but in invisible ink (aka, white). The only catch is that the tikzpicture also sets colors, so I need a new color command in there as well: \answercolor, which is either the color you give it, or white. (Also, \textcolor{white}{#1} doesn't like paragraph breaks either, so I switched to {\color{white}#1}.) (Note that the text is still present, so that this would not be suitable for giving as a pdf - you can copy and paste the invisible text. But once printed, the text is no longer there.

\documentclass[10pt]{article}

\usepackage{xcolor} \usepackage{tikz}

\newif\ifanswers %\answerstrue \answersfalse

\newcommand{\answerkeyA}[1]{% \ifanswers% \textcolor{red}{\smash{\textbf{\parbox[t]{\linewidth}{#1}}}}% \else% \textcolor{white}{\smash{\textbf{\parbox[t]{\linewidth}{#1}}}}% \fi% }

\newcommand{\answerkeyB}[1]{% \ifanswers% #1% \else% {\color{white}#1}% \fi% }

\newcommand{\answercolor}[1]{% \ifanswers% #1% \else% white% \fi% }

\begin{document}

\begin{enumerate} \item Answer the following question with words.

\answerkeyA{ I do not like them in a house. I do not like them with a mouse. I do not like them here or there. I do not like them anywhere. I do not like green eggs and ham. }

\vspace*{1in}

\item Draw a picture to illustrate the following problem.

\answerkeyB{ Answer:

\begin{tikzpicture}[scale=0.3,font=\sffamily] \draw [step=1.0, thin, \answercolor{gray!50}] (-3, -3) grid (3, 3); \draw [arrows={latex-latex}] (-3,0) -- coordinate (x axis mid) (3,0); \draw [arrows={latex-latex}] (0,-3) -- coordinate (y axis mid) (0,3); \end{tikzpicture} }

\vspace*{2in}

\item The next problem should be here. \end{enumerate}

\end{document}

Teepeemm
  • 6,708
  • Thank you for this solution. While a teacher who is more organized that I am might have the typeset solutions at the time the test is written, I don't. I also try to make everything student centered. What that means here is that I determine the formatting of the test by what is best for the student who is taking the exam. I try not to put page breaks in the middle of multi-part problems, for example. So bottom line, I want to overlay solutions on an existing test, not use the solutions to determine the spacing on the test. – Christopher Donham Aug 23 '20 at 14:58
  • Also, the original question asked for a single answerKey command that could format both of the answers given. This solution does not do that. If I try using answerKeyA for both situations in the original MWE, it does not work. – Christopher Donham Aug 23 '20 at 15:06
2

I think this is probably what you want. This solution provides answer environment, and you only need to put your answers in there. You can switch between teacher/student version by activating \answerfalse.

Teacher version:

teacher version

Student version:

student version

More details about this solution:

  1. I am using xparse to capture the body of answer environment. Note that this requires newer version of xparse. If you do not have access to it, please consider using environ package instead.
  2. The content is put into a breakable tcolorbox to measure height. After putting the content into input stream, tcolorbox will take care of breaking the content across several pages.
  3. It is assumed that you will not change \baselinestretch - you may need to modify the calculations if line spacing is changed.
  4. When \answerfalse is activated, I am filling the tcolorbox with new lines whose height is equal to original content.
  5. Because the body of the environment is captured as argument, you will not be able to use verbatim environments inside answer (e.g. align,align*,listing,etc.)
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{amsmath, amssymb}
\usepackage{expl3}
\usepackage{xparse}
\usepackage{tikz}
\usepackage{blindtext}
\usepackage[breakable, skins]{tcolorbox}

\ExplSyntaxOn \bool_new:N \g_show_answer_bool \coffin_new:N \g_answer_coffin \dim_new:N \g_answer_height_dim \dim_new:N \g_answer_parbox_height_dim

% unit height of each \parbox for tcolorbox's % page breaking algorithm to work with \dim_gset:Nn \g_answer_parbox_height_dim {\baselineskip}

\newcommand{\answertrue}{\bool_gset_true:N \g_show_answer_bool} \newcommand{\answerfalse}{\bool_gset_false:N \g_show_answer_bool}

% shows answer by default \answertrue

\cs_set:Npn \my_tcbox:nn #1#2 { \begin{tcolorbox}[breakable, enhanced, left=0pt, right=0pt, top=0pt, bottom=0pt, boxsep=0pt, boxrule=0pt, colback=white, colframe=white, width=\linewidth, #2] #1 \end{tcolorbox} }

\cs_generate_variant:Nn \my_tcbox:nn {Vn}

\cs_set:Npn \my_height_box:n #1 { \exp_not:n{\parbox{\linewidth}{\rule{0pt}{#1}}} }

% +b (body capture) requires xparse package to be no older than 2019-03-05 release % or one can use environ package to achieve similar effects \DeclareDocumentEnvironment{answer}{+b}{ \bool_if:NTF \g_show_answer_bool { % shows answer \my_tcbox:nn {#1} {} } { % otherwise, use content that has identical height instead % put content in a minipage to measure height \hcoffin_gset:Nn \g_answer_coffin { \my_tcbox:nn {#1} {} }

    % get height of content
    \dim_gset:Nn \g_answer_height_dim {
        \coffin_dp:N \g_answer_coffin + 
        \coffin_ht:N \g_answer_coffin +
        \baselineskip
    }

    % check if height of content is longer than current page
    \fp_compare:nNnT {\g_answer_height_dim} > {\pagegoal - \pagetotal} {
        % if so, increase \g_answer_height_dim
        \dim_gadd:Nn \g_answer_height_dim {\baselineskip}
    }

    % compute how many units we need
    \fp_set:Nn \l_tmpa_fp {floor(\g_answer_height_dim / \g_answer_parbox_height_dim)}
    % create token list to fill vspace
    \tl_set:Nn \l_tmpa_tl {}
    \fp_step_inline:nnnn {1} {1} {\l_tmpa_fp} {
        \tl_put_right:Nn \l_tmpa_tl {\par\phantom{}}
    }
    % compute space less than \g_answer_parbox_height_dim
    \fp_set:Nn \l_tmpb_fp {\g_answer_height_dim - \l_tmpa_fp * \g_answer_parbox_height_dim}
    % fill remaining space
    \tl_put_right:Nx \l_tmpa_tl {\exp_not:N\vspace*{\fp_eval:n {\l_tmpb_fp} pt}}
    %\tl_show:N \l_tmpa_tl
    \my_tcbox:Vn \l_tmpa_tl {}
}

}{} \ExplSyntaxOff

% toggle version w/ or w/out answer %\answerfalse

\begin{document}

\begin{enumerate}

\item Answer the following question with words.

\begin{answer} I do not like them in a house. I do not like them with a mouse.
I do not like them here or there. I do not like them anywhere.
I do not like green eggs and ham. \end{answer}

\item Draw a picture to illustrate the following problem.

\begin{answer} \begin{tikzpicture}[scale=0.3,font=\sffamily] \draw [step=1.0, thin, gray!50] (-3, -3) grid (3, 3); \draw [arrows={latex-latex}] (-3,0) -- coordinate (x axis mid) (3,0); \draw [arrows={latex-latex}] (0,-3) -- coordinate (y axis mid) (0,3); \end{tikzpicture} \end{answer}

\item Write a long and useless article.

\begin{answer} \Blindtext[4] \end{answer}

\end{enumerate}

Space to top page top: \the\pagetotal

\end{document}

Alan Xiang
  • 5,227
  • I very much appreciate the effort that went into writing such a complicated environment as a solution for my problem. Like the solution from Teepeemm, this solution appears to rely on my having the answer typeset when writing the exam. The idea was to overlay the solution into the existing space so that the exam did not change -- think of it as you "taking the exam" old-school with pencil and paper. You don't get to change the formatting of the exam when you do the answer key. I'm trying to do effectively the same thing with LaTex. – Christopher Donham Aug 23 '20 at 15:16