Dashundergaps Code
Dashundergaps Code
Abstract
The dashundergaps package offers the possibility to replace material in running text
with white space in order to build up forms that can be filled in at a later time.
By default the gaps are underlined and followed by a gap number in parentheses, but
many other designs are possible, e.g., dashes or dots instead of the underline, no gap
numbers or a different format for them, gap widening for easier fill-in, etc.
There is also a teacher’s mode which shows the normally hidden text in a special
(customizable) format.
This is another article in a series of TUGboat articles describing small packages to
introduce coding practices using the expl3 programming language. See [1] for the first
article in the series. For more details on expl3 refer to [2].
Contents
1 Introduction 1
2 The user interface 2
2.1 Options to customize the gap display . . . . . . . . . . . . . . . . . 3
2.1.1 Gap modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.2 Gap formatting . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.3 Gap numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.4 Gap widening . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3 Differences from the original package 4
4 Solution to the puzzle 5
5 The implementation 6
5.1 Loading and fixing/changing ulem . . . . . . . . . . . . . . . . . . . 6
5.2 The main implementation part . . . . . . . . . . . . . . . . . . . . . 7
5.2.1 User interface commands . . . . . . . . . . . . . . . . . . . . . 7
5.2.2 Counters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5.2.3 Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5.2.4 Option handling . . . . . . . . . . . . . . . . . . . . . . . . . . 9
5.2.5 Closing shop . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Index 13
1 Introduction
The dashundergaps package provides a single command \gap which takes one argument
and produces a gap of the width of that argument. To better mark this gap it is
underlined in some form (could be a solid line, a dashed or dotted line or even a
wriggling line). Furthermore, gaps can be numbered to be able to easily refer to them.
::::::::::::
Figure 1 shows an example in the form of a fill-in puzzle.
As you see there, some gaps are numbered with a superscript number (not the default
setting) while others aren’t. How this is done and how to change the result is explained
in the next section.
There also exists a “teacher mode” in which the gaps are filled with the text given in
the argument. This can be used to show the correct answers of a test (as we do in
∗ This is a reimplementation (using expl3, the L ATEX3 programming language) of a package
originally written by Luca Merciadri in 2010. The current package version is v2.0h dated 2021-03-05.
And here are some hints for the puzzle if you want to fill it out:
1. If only everything would be that easy 3. Back then known as the doggie book.
to answer.
2. The author of the book “Last Chance 4. Old names die hard.
To See” and of a famous radio show.
The answers are given in Section 4, showing the gaps filled in using the so-called teacher mode,
which can be activated or deactivated at any point in the document.
Section 4) or to give a sample fill-in for a form, to help people fill it out correctly. The
“teacher mode” produces the same line breaks because it ensures that the fill-ins take
the same amount of space as the gaps.
Another important feature is the possibility to artificially widen the gaps, compared to
the textual material in the argument. After all, when a form is filled by hand people
typically need more space to write some text compared to the same text being typeset.
So making the gaps simply as wide as the material likely results in too little space.
Frank Mittelbach
TUGboat, Volume 0 (9999), No. 0 draft: March 5, 2021 17:59 3
number is toggled, i.e., if it would be produced because of the current option settings
it will be suppressed; if it is suppressed through an option it will be typeset. This way
one can select the most convenient setting via an option for the whole document and
use * to toggle it as needed.
Since \gap uses ulem’s commands it inherits the limitations of these commands; notably,
only simple text can be used in the htexti argument. For example, a \footnote couldn’t
be used in the argument (but then that wouldn’t make much sense in a gap, would it?).
Another important (and sometimes annoying) restriction is that any brace group
or command with arguments inside the ulem commands is set as if is is inside an
\mbox, in particular it will not break across lines. For example \gap{\emph{...}} will
show this behavior. The dashundergaps package tries mediate as best as possible, e.g.,
\gap{\mytext} works, but in other cases you have to live with this limitation.
gap-format-adjust A boolean (default true). If set, the “line” below the gap is
raised to be roughly at the baseline, which normally looks better when there is no
text above the line.
teacher-gap-format Another choice option, with the same values as gap-format,
used when we are in “teacher mode”, but this time the default is blank as normally
the gap text is typeset in the bold font and is therefore already identifiable, with
no need for additional underlining. However, depending on the circumstances it
might be helpful to keep the underlining (or use a different kind of underlining)
while in “teacher mode”.
gap-font This option expects a font directive as its value, e.g., \bfseries (which
is also the default). Using this option without supplying a value is equivalent
to supplying an empty value. It will be used to determine the font for the gap
material regardless of the mode. This is important to ensure that the gaps always
have the same width regardless of whether or not the material is shown.
For the example puzzle above it was set to \itshape, which you can see in the
puzzle answer.
dash Short name for gap-format=dash.
dot Short name for gap-format=dot.
Frank Mittelbach
TUGboat, Volume 0 (9999), No. 0 draft: March 5, 2021 17:59 5
• Stray spaces in the definition of \gap (that showed up in the output) have been
eliminated.
• Various combinations of options that didn’t work are now possible.
• Explicit hyphenations \- showed up in gap mode, now they can be used.
• Nesting isn’t possible for obvious reasons, but the fact is now detected and catered
to by ignoring the inner gap requests after generating an error.
• Option names have been normalized (though the original names are still available).
• The option phantomtext is no longer necessary, though still supported (with a
warning) as a no-op.
• The names of the LATEX counters used have changed, so if you directly addressed
them that would need changing.
• The font used in teacher mode (by default boldface) is now also used if gap mode
is chosen, to ensure that the output in all modes produces identical line breaks;
for the same reason, the ulem machinery is always used, even if not underlining
(or dashing, etc.).
• The gaps can be extended by a percentage or by a minimum amount to ensure that
there is enough space to fill in the text (given that hand-written text is typically
wider than typeset material); the values are adjustable.
• \gap now has an optional argument through which you can explicitly request the
type of underlining you want to use.
• \gap also supports a star form which toggles the setting of gap numbers.
• The use of \label within the \gap command argument allows for later reference
to that gap by its number (provided a gap number is typeset).
• The implementation is done with expl3, the programming language for LATEX3.
Although invisible to the user, in some sense that was the main purpose of the
exercise: to see how easy it is to convert a package and use the extended features
of expl3.
The initial ‘E.’ in Donald E. Knuth’s name stands for Ervin (5)
. The well-known answer to
the Ultimate Question of Life, the Universe, and Everything is 42 according to Douglas
Adams (6)
. The first edition of
The LATEX Companion (7) celebrates its silver anniversary
in 2019. Historically speaking, expl3 stands for EXperimental Programming Language 3
(8)
even though it is a production language these days.
5 The implementation
5.1 Loading and fixing/changing ulem
The first thing to do is to load ulem without changing \emph or \em:
1 h*packagei
2 \RequirePackage[normalem]{ulem}
The code in this section follows LATEX 2ε conventions, i.e., models the commands as
they look in the ulem package.
\dotuline The dots produced by \dotuline depend on the current font, which is a somewhat
questionable design — if you underline a text with a single bold word somewhere inside
it will change the shape of the dot line. So we always use the \normalfont dot (this
is not done in the original definition).
3 \protected\def\dotuline{\leavevmode\bgroup
4 \UL@setULdepth
5 \ifx\UL@on\UL@onin \advance\ULdepth2\p@\fi
6 \markoverwith{\begingroup
7 % \advance\ULdepth0.08ex
8 \lower\ULdepth\hbox{\normalfont \kern.1em .\kern.04em}%
9 \endgroup}%
10 \ULon}
\uwave The original \uwave used a hard-wired value of 3.5pt for the lowering. We change
that to be based on the current value of \ULdepth so that the user (or this package
here) can change the placement.
11 \protected\def\uwave{\leavevmode\bgroup
12 \UL@setULdepth
13 \advance\ULdepth 0.6\p@
14 \markoverwith{\lower\ULdepth\hbox{\sixly \char58}}\ULon}
\fmdug@ublank \fmdug@ublank underlines with blanks. Normally not especially useful (which is why
we make it internal), but if we want to have ulem acting, but without actually visibly
underlining, this is the command to use.
15 \def\fmdug@ublank{\bgroup\let\UL@leadtype\@empty\ULon}
\UL@dischyp We need to do a little patching to ensure that nothing is output by the ulem commands
\UL@putbox if we don’t want it to. So the next two commands are from ulem with \box replaced
by \fmdug@box so that we can change the behavior.
16 \def\UL@dischyp{\global\setbox\UL@hyphenbox\hbox
17 {\ifnum \hyphenchar\font<\z@ \string-\else \char\hyphenchar\font \fi}%
18 \kern\wd\UL@hyphenbox \LA@penalty\@M
19 \UL@stop \kern-\wd\UL@hyphenbox
20 \discretionary{\fmdug@box\UL@hyphenbox}{}{}\UL@start}
21 \def\UL@putbox{\ifx\UL@start\@empty \else % not inner
22 \vrule\@width\z@ \LA@penalty\@M
23 {\UL@skip\wd\UL@box \UL@leaders \kern-\UL@skip}%
24 \fmdug@box\UL@box \fi}
Frank Mittelbach
TUGboat, Volume 0 (9999), No. 0 draft: March 5, 2021 17:59 7
\fmdug@box By default we output the box in the commands above, but when we don’t want to
output anything visible we change the definition to generate a box with empty content
but the right size.
25 \let\fmdug@box\box
53 { \bool_set_false:N \l__fmdug_teacher_bool }
(End definition for \TeacherModeOn and \TeacherModeOff. These functions are documented on page
3.)
5.2.2 Counters
\c@gapnumber We have one user-level counter which is referenceable and holds the gap number of the
current gap. It can be reset to 0 to restart counting.
54 \newcounter{gapnumber}
\c@totalgapnumber We also keep track of all gaps ever made using another user-level counter. Since this
one is supposed to keep track of the total number of gaps, it makes little sense to
modify it at the document level. However, there may be use cases even for that and
more importantly, by making it a user-level counter it is possible to refer to the total
number of gaps easily, e.g., via \thetotalgapnumber.
55 \newcounter{totalgapnumber}
\l__fmdug_extra_left_gap_tl Two scratch token lists to enlarge the gap on the left or right side.
\l__fmdug_extra_right_gap_tl 57 \tl_new:N \l__fmdug_extra_left_gap_tl
58 \tl_new:N \l__fmdug_extra_right_gap_tl
\l__fmdug_gap_format_tl The gap formatting is normally handled by a ulem command; which one depends on
\l__fmdug_teacher_gap_format_tl the options used. To record the choice we store it in a token list (one for normal and
one for teacher mode).
59 \tl_new:N \l__fmdug_gap_format_tl
60 \tl_new:N \l__fmdug_teacher_gap_format_tl
5.2.3 Messages
61 \msg_new:nnn {dashundergaps} {deprecated}
62 { The~ #1~ ‘#2’~ you~ used~ \msg_line_context: \ is~ deprecated~ and~
63 there~ is~ no~ replacement.~ Since~ I~ will~ not~ guarantee~ that~
64 #1~ ‘#2’~ will~ be~ kept~ forever~ I~ strongly~ encourage~ you~
65 to~ remove~ it~ from~ your~ document. }
66 \msg_new:nnnn {dashundergaps} {nested}
67 { The~ \gap command~ can’t~ be~ nested! }
68 { Nesting~ doesn’t~ make~ much~ sense~ as~ the~ inner~ one~
69 wouldn’t~ be~ visible.~ ~ To~ allow~ further~ processing~ it~ is~
70 handled~ as~ if~ it~ hasn’t~ been~ asked~ for. }
71 \msg_new:nnnn {dashundergaps} {gap-format-value}
72 { Unknown~ value~ for~ key~ ’#1 gap-format’! }
Frank Mittelbach
TUGboat, Volume 0 (9999), No. 0 draft: March 5, 2021 17:59 9
Frank Mittelbach
TUGboat, Volume 0 (9999), No. 0 draft: March 5, 2021 17:59 11
168 % ====================================
169 ,teachermode .meta:n = { teacher-mode }
170 ,dash .meta:n = { gap-format = dash }
171 ,dot .meta:n = { gap-format = dot }
172 ,displaynbgaps .meta:n = { display-total-gaps }
173 % ------------------
174 ,phantomtext
175 .code:n = \msg_warning:nnnn{dashundergaps}{deprecated}
176 {option}{phantomtext}
177 % ====================================
178 }
\__fmdug_gap:nnn At last, here comes the action. \__fmdug_gap:nn expects three arguments: #1 identifies
if a star was present, #2 indicates what kind of “underlining” is wanted (anything not
recognized is ignored, in particular “–NoValue–” if \gap was used without an optional
argument) and #3 is the material to produce a gap for.
179 \cs_new:Npn\__fmdug_gap:nnn #1#2#3 {
180 \group_begin:
Define the font used inside the gap. We need to do this up front since we want to
measure the text (and that needs the correct font already).
181 \l__fmdug_font_tl
Nesting is not supported so inside the gap we redefine \__fmdug_gap:nnn to raise an
error and just return the third argument if it is encountered again.
182 \cs_set:Npn \__fmdug_gap:nnn ##1##2##3
183 {
184 \msg_error:nn{dashundergaps}{nested}
185 ##3
186 }
We always increment the counter for the total number of gaps, but increment the
gapnumber only if we are displaying it. For the latter one we use \refstepcounter to
make it referenceable.
187 \stepcounter{totalgapnumber}
188 \bool_xor:nnT { #1 } { \l__fmdug_number_bool }
189 { \refstepcounter{gapnumber} }
Next we prepare for widening if that is being asked for: Measure the width of the text
and then set \l__fmdug_extend_dim to be the requested percentage divided by two
of that width (since we add it later on both sides).
190 \bool_if:NTF \l__fmdug_gap_widen_bool
191 {
192 \settowidth \l__fmdug_extend_dim {#3}
193 \dim_set:Nn \l__fmdug_extend_dim
194 { \l__fmdug_gap_percent_tl \l__fmdug_extend_dim / 200 }
Then compare it to the minimum / 2 and choose whatever is larger.
195 \dim_compare:nNnT \l__fmdug_extend_dim < { .5\l__fmdug_gap_min_dim }
196 { \dim_set:Nn \l__fmdug_extend_dim { .5\l__fmdug_gap_min_dim } }
Now we prepare what needs to go to the left and the right of the gap.
197 \tl_set:Nn \l__fmdug_extra_left_gap_tl
198 { \hbox_to_wd:nn\l__fmdug_extend_dim{} \allowbreak }
199 \tl_set:Nn \l__fmdug_extra_right_gap_tl
200 { \allowbreak \hbox_to_wd:nn\l__fmdug_extend_dim{} }
201 }
And if no widening is asked for we clear these two token lists so they don’t do anything.
202 {
203 \tl_clear:N \l__fmdug_extra_left_gap_tl
204 \tl_clear:N \l__fmdug_extra_right_gap_tl
205 }
Next comes deciding the gap format. If in teacher mode it will be whatever is in \l__
fmdug_teacher_gap_tl. Otherwise, either it is based on the content of the optional
argument or, if that is not given or unknown, it will be \l__fmdug_gap_format_tl.
206 \bool_if:NTF \l__fmdug_teacher_bool
207 { \l__fmdug_teacher_gap_format_tl }
208 {
But before we execute any of the ulem commands we make sure that they do not
output text.
209 \cs_set:Npn \fmdug@box ##1 {\hbox_to_wd:nn{\box_wd:N ##1}{}}
210 \str_case:nnF {#2}
211 {
212 {u} { \__fmdug_gap_format_adjust:n{.4pt} \uline }
213 {d} { \__fmdug_gap_format_adjust:n{2pt} \uuline }
214 {w} { \__fmdug_gap_format_adjust:n{1pt} \uwave }
215 {b} { \fmdug@ublank }
216 {.} { \__fmdug_gap_format_adjust:n{-.08ex} \dotuline }
217 {-} { \__fmdug_gap_format_adjust:n{0pt} \dashuline }
218 }
219 { \l__fmdug_gap_format_tl }
220 }
Whatever was decided as the gap format, it needs one argument, i.e., the material
(with possible gap extension on both sides).
221 {\l__fmdug_extra_left_gap_tl #3 \l__fmdug_extra_right_gap_tl }
Finally we typeset the gap number if that was requested.
222 \bool_xor:nnT { #1 } { \l__fmdug_number_bool }
223 { \l__fmdug_gapnum_format_tl }
Close the group from above to keep any of the redefinitions confined.
224 \group_end:
225 }
226 \cs_generate_variant:Nn \__fmdug_gap:nnn {nno}
(End definition for \__fmdug_gap:nnn.)
\__fmdug_display_total_gaps: This command will display the total number of gaps if requested. The hard-wired
formatting comes from the first version of the package.
227 \cs_new:Npn \__fmdug_display_total_gaps: {
228 \vfill \centering
229 \bfseries Total~ Gaps:~ \thetotalgapnumber
230 }
(End definition for \__fmdug_display_total_gaps:.)
Frank Mittelbach
TUGboat, Volume 0 (9999), No. 0 draft: March 5, 2021 17:59 13
So what remains to be done is executing all options passed to the package via
\usepackage.
235 \ProcessKeysPackageOptions{fmdug}
236 h/packagei
References
[1] Frank Mittelbach. The widows-and-orphans package. TUGboat 39:3, 20018.
https://ctan.org/pkg/widows-and-orphans
[2] LATEX3 Project Team. A collection of articles on expl3.
https://latex-project.org/publications/indexbytopic/l3-expl3/
Index
The italic numbers denote the pages where the corresponding entry is described,
numbers underlined point to the definition, all others indicate the places where it is
used.
Symbols \g__fmdug_display_total_gaps_bool
\\ . . . . . . . . . . . . . . . . . . . . . . . . . . 73 . . . . . . . . . . . . . . . . . . . 161, 232
\l__fmdug_extend_dim . . . . . 11,
A 56, 192, 193, 194, 195, 196, 198, 200
\allowbreak . . . . . . . . . . . . . . . 198, 200 \l__fmdug_extra_left_gap_tl . . . .
\AtEndDocument . . . . . . . . . . . . . . . . 231 . . . . . . . . . . . . . 57, 197, 203, 221
\l__fmdug_extra_right_gap_tl . . .
B . . . . . . . . . . . . . 57, 199, 204, 221
\bfseries . . . . . . . . . . . . . . . . 167, 229 \l__fmdug_font_tl . . . . . . . 165, 181
bool commands: \__fmdug_gap:nn . . . . . . . . . . . . 11
\bool_if:NTF . . . . . . . 190, 206, 232 \__fmdug_gap:nnn . . . . 7, 11, 47, 179
\bool_set_false:N . . ......... 53 \__fmdug_gap_format_adjust:n . . .
\bool_set_true:N . . . ......... 51 . . . . . . . . . . . . . 88, 91, 94, 97,
\bool_xor:nnTF . . . . . . . . . 188, 222 100, 111, 114, 212, 213, 214, 216, 217
box commands: \l__fmdug_gap_format_tl . . . 12,
\box_wd:N . . . . . . . .......... 209 59, 87, 90, 93, 96, 99, 102, 219
\l__fmdug_gap_min_dim 146, 195, 196
C
\l__fmdug_gap_percent_tl . 149, 194
\centering . . . . . . . . . . . . . . . . . . . 228
\l__fmdug_gap_widen_bool . 140, 190
cs commands:
\l__fmdug_gapnum_format_tl 158, 223
\cs_generate_variant:Nn . . . . . 226
\l__fmdug_number_bool 152, 188, 222
D \l__fmdug_teacher_bool . . . . . . . . .
\dashuline . . . . . . . . . 2, 2, 94, 128, 217 . . . . . . . . . . . . 51, 53, 78, 82, 206
\dashundergapsdate . . . . . . . . . . . . . 41 \l__fmdug_teacher_gap_format_tl .
\dashundergapssetup . . . . . . . . 3, 9, 48 59, 124, 126, 128, 130, 132, 134, 207
\dashundergapsversion . . . . . . . . . . 42 \l__fmdug_teacher_gap_tl . . . . . 12
\DeclareDocumentCommand . . . 44, 50, 52 \footnote . . . . . . . . . . . . . . . . . . . . . 3
dim commands: G
\dim_compare:nNnTF . . . . . . . . . 195 \gap . . . . . . . . . 1, 2, 3, 3, 5, 7, 11, 44, 67
\dotuline . . . . . . 2, 2, 3, 6, 97, 130, 216 group commands:
\group_begin: . . . . . . . . . . . . . 180
E
\group_end: . . . . . . . . . . . . . . . 224
\em . . . . . . . . . . . . . . . . . . . . . . . . . . 6
\emph . . . . . . . . . . . . . . . . . . . . . . . 2, 6 H
\endinput . . . . . . . . . . . . . . . . . . . . 38 hbox commands:
\hbox_to_wd:nn ..... 198, 200, 209
F
fmdug internal commands: K
\__fmdug_display_total_gaps: . . . keys commands:
. . . . . . . . . . . . . . . . . . . 227, 233 \keys_define:nn . . . . . . . . . . . . 75
Frank Mittelbach
Mainz, Germany
https://www.latex-project.org
https://ctan.org/pkg/dashundergaps
Frank Mittelbach