252

TUGboat, Volume 39 (2018), No. 3
The widows-and-orphans packageFrank Mittelbach
Abstract
The widows-and-orphans package checks page or column breaks for issues with widow ororphan lines and issues warnings if such problems are detected. In addition, it checks
and complains about breaks involving hyphenated words and warns about display
formulas directly after a page break — if they are allowed by the document parametersettings, which by default isn’t the case.
A general discussion of the problem of widows and orphans and suggestions for resolution
is given in [1].
Contents1

2

1

Overview
1.1 Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 User commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Related packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
The implementation
2.1 Checking \outputpenalty . . . . . . . . . . . . . . . . . . . . . . .
2.2 Messages to the user . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3 Adjusting parameter values . . . . . . . . . . . . . . . . . . . . . . .
2.4 The option setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.5 Document-level commands . . . . . . . . . . . . . . . . . . . . . . .

252
252
253
253
253
254
256
258
261
262

Overview

To determine if a widow or orphan has occurred at a column or page break, thepackage analyzes the \outputpenalty that triggered the break. As TEX adds special
penalties to widow and orphan lines (\widowpenalty and \clubpenalty), we canhope to identify them, provided the penalties have unique values so that we don’t end
up with false positives.
The package therefore analyzes the different parameter values and if necessary adjusts
the settings slightly so that all possible combinations that can appear in documents
have unique values and can thus be identified.
All that remains is to hook into the output routine check; if \outputpenalty has avalue that matches one of the problematic cases, issue a warning.
Besides widows and orphans it is possible to detect other cases controlled throughpenalty parameters, e.g., \brokenpenalty that is added if a line ends in a hyphen. So
by including this parameter into the checks, we can identify when that happens at theend of a column and issue a warning there too.
We also do this for \predisplaypenalty, which controls a break just in front of amath display. This is normally set to 10000 so such breaks don’t happen in standard
LATEX, but if the value is lowered it becomes possible, and thus a possible issue.
1.1

Options

The package has a number of key/value options to adjust its behavior. The option checkdefines what happens when an issue is found: default is warning, other possibilities
are error, info and none.
The options orphans and widows set reasonable parameter values; the default is touse whatever the class defines. Possible values are prevent, avoid or default, thelatter meaning use standard LATEX defaults.
To set all parameters in one go you can use prevent-all, avoid-all or default-all.
These options also assign values to \brokenpenalty and \predisplaypenalty.
Frank Mittelbach

TUGboat, Volume 39 (2018), No. 3
1.2

253

User commands

The package provides three user-level commands.
\WaOsetup\WaOsetup hcomma list i

This command accepts any of the package options and allows adjusting the package
behavior in mid-document if necessary.
\WaOparameters\WaOparameters

This command produces a listing of the parameter combinations and their values.
\WaOignorenext\WaOignorenext

This command directs the package to not generate warnings for the current page orcolumns (if we know that they can’t be corrected).
1.3

Related packages

Package nowidow: This package offers some commands to help pushing lines fromone page to another by locally requesting no widows or orphans — possibly for several
lines. In that respect it implements one of the possibilities discussed in the TUGboat
article [1]. This is, however, in many cases not the best solution to get rid of a widow
or orphan when the interest is to achieve high typographical quality.
2

The implementation

The package is implemented in expl3, so the first thing we do is to define a prefix for
our internal commands, so that we can write @@ in the source when we mean the prefix__fmwao, indicating that something is internal.1
1

h@@=fmwaoi

Then we check that we are running on top of LATEX 2ε and load the two packages wewant to use: xparse for the user interface and l3keys2e for key/value option syntax.
They load the needed expl3 code so we are ready to roll afterwards.
2\NeedsTeXFormat{LaTeX2e}\RequirePackage{xparse,l3keys2e}

Then we announce the package to the world at large. This declaration will also tell
LATEX that this is an expl3 package and that from now on the expl3 conventions are tobe obeyed, e.g., _ and : can appear in command names and whitespace is automaticallyignored (so no need for % all over the place).2
3
4\@makecol\ProvidesExplPackage{widows-and-orphans}{2018/11/18}{v1.0b}{Detecting widows and orphans (FMi)}

As mentioned in the introduction we want to check the value of \outputpenalty inside
the output routine, and so we need to add some code to the output routine.
We add it to the front of \@makecol, the macro that assembles the actual column.
This way it only gets executed if we make a column or a page but not when the output
routine is triggered because of a float or \marginpar.
5\tl_put_left:Nn \@makecol { \__fmwao_test_for_widows_etc: }(End definition for \@makecol.)1 l3docstrip expands that for us, so in the .sty file we get the longer names with double
underscores even though the real source just contains @@ all over the place. The same is done by thel3doc class for the printed documentation you are currently reading.
2 Figure 1 gives a short introduction to the naming conventions of expl3 for those readers whohaven’t seen any code written in expl3. For more details refer to [2].

The widows-and-orphans package

254

TUGboat, Volume 39 (2018), No. 3

Commands in expl3 use the following naming convention:\hmodule i_haction i:harg-spec i\__hmodule i_haction i:harg-spec i% externally available command% internal command local to the package

hmodule i describes the (main) area to which the command belongs,
haction i describes the (main) action of the command, andharg-spec i shows the expected command arguments and their preprocessing:N means expect a single token;
n means expect a (normal) braced argument;
T and F also represent braced arguments, indicating “true” and “false” branches in a conditional;
V means expect a single token, interpret it as a variable and pass its value on to the command;
Finally, p stands for a (possibly empty) parameter spec, e.g., #1#2... in a definition.
There are a number of other argument types, but they aren’t used in the code described here.
Examples:\cs_if_eq:NNTF is a conditional from the module cs (command names) implementing the action
if_eq (if equal) and expecting two single tokens (commands to compare) and a true and a false
branch (one of which is executed).
\tl_put_left:Nn is a function from the module tl (token lists) implementing put_left andexpecting a single token (a token list variable) and a braced argument (the data to insert at
the front/left in the variable).
Variables start with \l_ (for local) or \g_ (for global) and have the data type as the last part of thename. Variables internal to the package use two underscores, e.g., \l__fmwao_gen_warn_bool.
Figure 1: Crash course in expl3 command name conventions

2.1\g__fmwao_gen_warn_bool

Checking \outputpenalty

To be able to suppress checking we define a global boolean variable which by default is
set to true (warnings enabled).
6
7\bool_new:N \g__fmwao_gen_warn_bool\bool_gset_true:N \g__fmwao_gen_warn_bool(End definition for \g__fmwao_gen_warn_bool.)\__fmwao_test_for_widows_etc:

What are the different values related to orphans and widows and the like that canappear in \outputpenalty? Here is the basic list:\widowpenalty + \interlinepenalty → if the break happens on the second-last lineof a paragraph and the paragraph is not followed by a math display.
\displaywidowpenalty + \interlinepenalty → if the break happens on the secondlast line of a paragraph and the paragraph is followed by a math display.
\clubpenalty + \interlinepenalty → if the break happens after the first line of aparagraph and the paragraph has more than two lines.
\clubpenalty + \widowpenalty + \interlinepenalty → if the break happens after
the first line of a paragraph in a two-line paragraph (thus this line is also thesecond-last line).
\clubpenalty + \displaywidowpenalty + \interlinepenalty → if the break happens after the first line of a paragraph in a two-line paragraph and a math display
follows.
That’s it for widows and orphans. If we also consider hyphenated breaks then we get afurther set of cases, namely all of the above with \brokenpenalty added in and thecase of \brokenpenalty on its own (with just \interlinepenalty added).
Frank Mittelbach

TUGboat, Volume 39 (2018), No. 38255\cs_new:Npn \__fmwao_test_for_widows_etc: {So here is the main test. We compare \outputpenalty with each case until we have a
hit or run out of cases. Instead of adding \interlinepenalty to each case we subtract
it once from \outputpenalty to make the comparison a little more efficient.
If we get a hit, we execute the corresponding code: either \__fmwao_problem_
identified:n or \__fmwao_problem_identified:nn (i.e., one or two arguments)
to issue the warning. The arguments are to select the correct warning text and customize it further if necessary. For example, in the first case it is a “widow” problem
and the text in the message should start with “Widow” while in the second case it isalso a “widow” problem but the text will say “Display~ widow”.3 This just saves a
bit of space when different cases share more or less the same message text.
9
10
11
12
13
14
15
16
17
18
19
20

\int_case:nnF { \outputpenalty - \interlinepenalty }{{ \widowpenalty }{ \__fmwao_problem_identified:nn{widow}{Widow} }{ \displaywidowpenalty }{ \__fmwao_problem_identified:nn{widow}{Display~ widow} }{ \clubpenalty }{ \__fmwao_problem_identified:n{orphan} }{ \clubpenalty + \widowpenalty }{ \__fmwao_problem_identified:nn{orphan-widow}{} }{ \clubpenalty + \displaywidowpenalty }{ \__fmwao_problem_identified:nn{orphan-widow}{display} }A similar issue comes from a hyphen at the end of a column or page in which case TEX
adds \brokenpenalty so we can test against that:{ \brokenpenalty }{ \__fmwao_problem_identified:n{hyphen} }21
22

However, I said “TEX adds”, which means if a widow line also ends in a hyphen thenthe penalty will be the sum of both individual penalties. So all the cases above need tobe repeated with \brokenpenalty added to the value. We generate the same warnings,
though — e.g., we will say “Widow detected” and not “Hyphenated widow line detected”
as the latter seems to be overkill.
{ \brokenpenalty + \widowpenalty }{ \__fmwao_problem_identified:nn{widow}{Widow} }{ \brokenpenalty + \displaywidowpenalty }{ \__fmwao_problem_identified:nn{widow}{Display~ widow} }{ \brokenpenalty + \clubpenalty }{ \__fmwao_problem_identified:n{orphan} }{ \brokenpenalty + \clubpenalty + \widowpenalty }{ \__fmwao_problem_identified:nn{orphan-widow}{} }{ \brokenpenalty + \clubpenalty + \displaywidowpenalty }{ \__fmwao_problem_identified:nn{orphan-widow}{display} }

23
24
25
26
27
28
29
30
31
32

Finally there is \predisplaypenalty that we may as well check also (in case it was
set to a value lower than 10000). If it appears it means we have a display at the very
top of the page. We reuse the “widow” warning but this time say “Lonely~ display”
in the second argument. This case does not have \interlinepenalty added by TEX,
so we have to undo the optimization above.
{ \predisplaypenalty - \interlinepenalty }{ \__fmwao_problem_identified:nn{widow}{Lonely~ display} }

33
34
35

}
3 If you haven’t seen much expl3 code you may wonder about the ~. As the code ignores spaces wehave to mark up real spaces and for this the tilde is used. In expl3 code this does not act as a tie, but
simply as a catcode 10 space character (while normal spaces are ignored).

The widows-and-orphans package

256

TUGboat, Volume 39 (2018), No. 3
The last argument of \int_case:nnF is executed in the “false” case, i.e., when no
match has been found. In that case we check the status of \g__fmwao_gen_warn_bool
and if that is also “false”, i.e., we have been asked not to generate warnings, we issue
an error message. Why? Because the user asked us explicitly to ignore problemson the current page, but we found nothing wrong. This either means a problem got
corrected or the request was intended for a different page. Either way it is probably
worth checking.
{ \bool_if:NF \g__fmwao_gen_warn_bool{ \msg_error:nn{widows-and-orphans}{no-problem} } }36
37

Finally, we make sure that the next page or column is again checked.
\bool_gset_true:N \g__fmwao_gen_warn_bool

38
39

}

(End definition for \__fmwao_test_for_widows_etc:.)\__fmwao_problem_identified:n\__fmwao_problem_identified:nn

These commands prepare for generating a warning, but only if we are supposed to, i.e.,
if \g__fmwao_gen_warn_bool is true.
\cs_new:Npn \__fmwao_problem_identified:n #1 {\bool_if:NT \g__fmwao_gen_warn_bool42{ \msg_warning:nn{widows-and-orphans}{#1} }43 }44 \cs_new:Npn \__fmwao_problem_identified:nn #1 #2 {45\bool_if:NT \g__fmwao_gen_warn_bool46{ \msg_warning:nnn{widows-and-orphans}{#1}{#2} }
47 }
40
41

(End definition for \__fmwao_problem_identified:n and \__fmwao_problem_identified:nn.)

2.2\__fmwao_this_page:\__fmwao_next_page:

Messages to the user

For displaying nice messages to the user we need a few helper commands. The twohere show the page number of the current or next page. They are semi-smart, that isthey will recognize if the document uses roman numerals and if so display the number
as a roman numeral (but in all other cases it uses arabic numerals).
48
49\cs_new:Npn \__fmwao_this_page: { \__fmwao_some_page:n\c@page}\cs_new:Npn \__fmwao_next_page: { \__fmwao_some_page:n { \c@page + 1 } }(End definition for \__fmwao_this_page: and \__fmwao_next_page:.)\__fmwao_some_page:n\__fmwao_roman_thepage:

This macro first compares \thepage against the code that would be used in the caseof a roman numeral representation, and then displays its argument using either arabic
numbers or roman numerals.
\cs_new:Npn \__fmwao_some_page:n #1 {\cs_if_eq:NNTF \thepage \__fmwao_roman_thepage:52{ \int_to_roman:n } { \int_to_arabic:n }53{ #1 }
54 }50

51\__fmwao_roman_thepage: just stores the default definition of \thepage if page numbers are represented by roman numerals for use in the comparison above.
55\cs_new_nopar:Npn \__fmwao_roman_thepage: {\csname @roman\endcsname \c@page}(End definition for \__fmwao_some_page:n and \__fmwao_roman_thepage:.)\legacy_switch_if:nTF

To evaluate LATEX 2ε boolean switches in a nice way, we need a conditional. Eventually
this will probably make it into the expl3 code in this or a similar form, but right now
it is missing.
Frank Mittelbach

TUGboat, Volume 39 (2018), No. 3257\prg_new_conditional:Npnn \legacy_switch_if:n #1 {p, T , F , TF }{ \exp_args:Nc\if_meaning:w { if#1 } \iftrue \prg_return_true:58\else: \prg_return_false: \fi: }56
57(End definition for \legacy_switch_if:nTF.)

The first message is issued if we have been directed to ignore a problem and there
wasn’t one:
\msg_new:nnnn {widows-and-orphans} {no-problem}{ No~ problem~ to~ suppress~ on~ this~ page! }61{ Suppression~ of~ a~ widow~ or~ orphan~ problem~ was~ requested~
62
but~ on~ the~ current~ page~ there~ doesn’t~ seem~ to~ be~ any.~
63
Maybe~ the~ text~ was~ changed~ and~ the~ request~ should~ get~64(re)moved?}
59
60

The next message is about orphans. They can appear at the bottom of the first or thesecond column of the current page, if we are in two-column mode. So we check for thisand adjust the message accordingly.
\msg_new:nnnn {widows-and-orphans} {orphan}{ Orphan~ on~ page~ \__fmwao_this_page:67\legacy_switch_if:nT {@twocolumn}
68{ \space ( \legacy_switch_if:nTF {@firstcolumn}
69{ first~ } { second~ } column) }70}
71{ Check~ out~ the~ page~ and~ see~ if~ you~ can~ avoid~ the~ orphan.}65

66

A hyphen at the end of a page or column requires more or less the same message, so
this could have been combined with the previous one.
\msg_new:nnnn {widows-and-orphans} {hyphen}{ Hyphen~ in~ last~ line~ of~ page~ \__fmwao_this_page:74\legacy_switch_if:nT {@twocolumn}
75{ \space ( \legacy_switch_if:nTF {@firstcolumn}
76{ first~ } { second~ } column) }77}
78{ Check~ out~ the~ page~ and~ see~ if~ you~ can~ get~79
a~ better~ line~ break. }72

73

Widows need a different logic since we detect them when cutting a previous page orcolumn but the widow is on the following one. This message works for “widows”,“display widows” as well as math displays by just changing the first word (or words),
so here we use an additional argument:\msg_new:nnnn {widows-and-orphans} {widow}{ #1~ on~ page~82\legacy_switch_if:nTF {@twocolumn}
83{ \legacy_switch_if:nTF {@firstcolumn}
84{ \__fmwao_this_page: \space (second~ }85{ \__fmwao_next_page: \space (first~ }86column)87}
88{ \__fmwao_next_page: }89}
90{ Check~ out~ the~ page~ and~ see~ if~ you~ can~ avoid~ the~ widow.}
80

81

The case of both widow and orphan is similar, but we obviously need different text so
we made it its own message.
The widows-and-orphans package

258

TUGboat, Volume 39 (2018), No. 3\msg_new:nnnn {widows-and-orphans} {orphan-widow}{ Orphan~93\legacy_switch_if:nTF {@twocolumn}
94{ \legacy_switch_if:nTF {@firstcolumn}95{ and~ #1 widow~ on~ page~ \__fmwao_this_page: \space
96(first~ and~ second~ }97{ on~ page~ \__fmwao_this_page: \space (second~ column)~
98
and~ #1 widow~ on~ page~ \__fmwao_next_page: \space (first~ }99}
100{ on~ page~ \__fmwao_this_page: \space (second~ column)~
101
and~ #1 widow~ on~ page~ \__fmwao_next_page: \space (first~ }102
column)103}
104{ Check~ out~ the~ page~ and~ see~ if~ you~ can~ avoid~ both~105
orphan~ and~ widow.}
91
92

2.3

Adjusting parameter values

To avoid (a lot of) false positives during checking it is important that the parametervalues are chosen in a way that all possible combinations lead to unique \outputpenalty
values. At the same time, we want them to be as close as possible to the values thathave been initially requested by the user (or in the document class) and if we deviate
too much then this will likely alter the page breaks TEX finds. So here is an outline of
how we handle the parameters:• We set up a property list to hold penalty values that can appear in \outputpenalty
inside the output routine. The penalties are the “keys” and the corresponding
property list value is the source of how they got produced. For example, the key
might be 150 and the value \widowpenalty.
• Initially the property list is empty. So adding the first item simply means taking
the value of one parameter, say 150 from \widowpenalty + \interlinepenalty,
as the key and this formula as the property list value.
• For the next parameter, say \clubpenalty, we check if its value (or more precisely
its value plus \interlinepenalty) is already a key in the property list. If that isthe case, then we have failed and must modify the parameter value somehow.
• If not, we also have to check any combination of the current parameter with any
parameter processed earlier. If that combination is possible, e.g., \clubpenalty(new) and \widowpenalty (already processed) then we also have to check the sum.
If that sum is already a key in the property list then we have failed as well.
• If we have failed, we iterate by incrementing the current parameter value and try
again. Eventually we will get to a value where all combinations we test work, that
is, are not yet in the property list.
• We then change the parameter to this value and add all the combinations we
tried before to the property list (that is \clubpenalty + \interlinepenalty
both alone and together with \widowpenalty in our example). Thus from now
on those are also forbidden values.
• We do all this with a helper command that takes the new parameter as the firstargument and the list of different cases to try as a comma-separated list as asecond argument, e.g.,\__fmwao_decide_penalty:Nn \clubpenalty{ \clubpenalty + \interlinepenalty ,\clubpenalty + \widowpenalty + \interlinepenalty }• This way we are adding all relevant parameters to the property list and at thesame time adjusting their values if needed.

Frank Mittelbach

TUGboat, Volume 39 (2018), No. 3

259• Once all parameters are handled the property list is no longer needed as theparameters got changed along the way, but we keep it around as it allows for a
simple display of all settings in one go.
\l__fmwao_penalties_prop

Here is the property list for our process.
106\prop_new:N \l__fmwao_penalties_prop(End definition for \l__fmwao_penalties_prop.)\__fmwao_initialize:

Now we are ready to go. The first action is to clear the property list as the initialization
may happen several times.
107
108\cs_new:Npn \__fmwao_initialize: {\prop_clear:N \l__fmwao_penalties_prop

When TEX breaks a page at a glue item with no explicit penalty involved it sets\outputpenalty to 10000 in the output routine to distinguish it from a case where
an explicit penalty of 0 was in the document. That means none of our parameters or
parameter combinations can be allowed to have that particular value, because otherwise
we would get a false match for each break at glue and report an issue. So we enter
that value first (by hand) so that it will not be used by a parameter or parameter
combination.
\prop_put:Nnn \l__fmwao_penalties_prop {10000} {break~ at~ glue}

109

The next thing is to add the values for \@lowpenalty, \@medpenalty, \@highpenalty
to the property list as they also may show up in \outputpenalty if a user says, forexample, \nopagebreak[2].
Such a penalty from an explicit page break request does not get \interlinepenalty
added in.
\__fmwao_decide_penalty:Nn \@lowpenalty { \@lowpenalty}\__fmwao_decide_penalty:Nn \@medpenalty { \@medpenalty}\__fmwao_decide_penalty:Nn \@highpenalty { \@highpenalty}

110
111
112

Then comes the first real parameter for the orphans:\__fmwao_decide_penalty:Nn \clubpenalty{ \clubpenalty + \interlinepenalty }

113
114

followed by the one for the widows and the one for the display widows:\__fmwao_decide_penalty:Nn \widowpenalty{ \widowpenalty + \interlinepenalty ,\widowpenalty + \clubpenalty + \interlinepenalty }

115
116
117
118

\__fmwao_decide_penalty:Nn \displaywidowpenalty{ \displaywidowpenalty + \interlinepenalty ,\displaywidowpenalty + \clubpenalty + \interlinepenalty }

119
120
121

\brokenpenalty can appear on its own, and also with each and every combination we
have seen so far:\__fmwao_decide_penalty:Nn \brokenpenalty{ \brokenpenalty + \interlinepenalty ,\brokenpenalty + \clubpenalty + \interlinepenalty ,\brokenpenalty + \widowpenalty + \interlinepenalty ,\brokenpenalty + \widowpenalty + \clubpenalty + \interlinepenalty ,\brokenpenalty + \displaywidowpenalty + \clubpenalty+ \interlinepenalty }

122
123
124
125
126
127
128

Finally we have the parameter for lonely displays (again without \interlinepenalty
being added):\__fmwao_decide_penalty:Nn \predisplaypenalty { \predisplaypenalty }

129
130

}

The widows-and-orphans package

260

TUGboat, Volume 39 (2018), No. 3
If we run the above code with LATEX’s default parameter settings in force it will makea few adjustments and the property list will afterwards contain the following entries:
The property list \l__fmwao_penalties_prop contains the pairs (withoutouter braces):> {10000} => {break at glue}> {51} => {\@lowpenalty }> {151} => {\@medpenalty }> {301} => {\@highpenalty }> {150} => {\clubpenalty +\interlinepenalty }> {152} => {\widowpenalty +\interlinepenalty }> {302} => {\widowpenalty +\clubpenalty +\interlinepenalty }> {50} => {\displaywidowpenalty +\interlinepenalty }> {200} => {\displaywidowpenalty +\clubpenalty +\interlinepenalty }> {100} => {\brokenpenalty +\interlinepenalty }> {250} => {\brokenpenalty +\clubpenalty +\interlinepenalty }> {252} => {\brokenpenalty +\widowpenalty +\interlinepenalty }> {402} => {\brokenpenalty +\widowpenalty +\clubpenalty +\interlinepenalty }> {300} => {\brokenpenalty +\displaywidowpenalty +\clubpenalty+\interlinepenalty }> {10001} => {\predisplaypenalty }.
(End definition for \__fmwao_initialize:.)\l__fmwao_tmp_int\l__fmwao_tmp_tl\l__fmwao_success_bool

For doing the calculations and insertions into the property list, we will also need an
integer register, a token list variable and another boolean variable.
\int_new:N \l__fmwao_tmp_int\tl_new:N\l__fmwao_tmp_tl
133 \bool_new:N \l__fmwao_success_bool
131132(End definition for \l__fmwao_tmp_int , \l__fmwao_tmp_tl , and \l__fmwao_success_bool.)\__fmwao_decide_penalty:Nn

This is the core command that does the real work of choosing values. Let’s recall thatits first argument is the parameter we are currently handling and the second argument
is a comma-separated list of cases for which we need to ensure that their results are
not yet in the property list.
134\cs_new:Npn \__fmwao_decide_penalty:Nn #1 #2 {We start by setting the boolean to false and then run a loop until we have found a
suitable parameter value that meets our criteria.
135
136\bool_set_false:N \l__fmwao_success_bool\bool_do_until:Nn \l__fmwao_success_bool

Inside the loop we start with the assumption that the current value of the parameter isfine and then check if that assumption is true. If yes, we can exit the loop, otherwise
we will have to try with a different value.
137{ \bool_set_true:N \l__fmwao_success_bool

For the verification we try each item in the second parameter to see if that is alreadyin the property list. This means evaluating the expression to get the penalty value andthen looking it up in the property list. If it is there, we have failed. In this case we set
the boolean back to false and break out of the loop over the second argument since
there is no point in testing further.
138
139
140
141
142
143

\clist_map_inline:nn { #2 }{ \int_set:Nn \l__fmwao_tmp_int {##1}\prop_get:NVNT\l__fmwao_penalties_prop \l__fmwao_tmp_int \l__fmwao_tmp_tl{ \clist_map_break:n {\bool_set_false:N\l__fmwao_success_bool} }}Frank Mittelbach

TUGboat, Volume 39 (2018), No. 3

261

Once we have finished, the boolean will tell us if we are successful so far. If yes, itmeans there was no conflict. We therefore add all combinations with this parameter tothe property list, as from now on they are forbidden as well.
So we map once more over the second argument and enter them:\bool_if:NTF \l__fmwao_success_bool{ \clist_map_inline:nn { #2 }{ \int_set:Nn \l__fmwao_tmp_int {##1}\prop_put:NVn \l__fmwao_penalties_prop \l__fmwao_tmp_int {##1}
} }

144
145
146
147
148

If we failed we increment the parameter value and retry:{ \int_incr:N #1 }

149

}

150
151

}

One place where we will run this code is at the beginning of the document (so thatchanges to the parameters in the document class or the preamble are picked up).
The other place is when the user changes any of the parameters in the middle of the
document via \WaOsetup.
152\AtBeginDocument { \__fmwao_initialize: }(End definition for \__fmwao_decide_penalty:Nn.)

2.4

The option setup

The options are fairly straightforward:
153\keys_define:nn {fmwao} {By default messages are given as warnings above. If anything else is wanted the option
check can be used which simply changes the message class used internally:
154
155
156
157
158
159
160
161
162

,check .choice:,check / error.code:n = \msg_redirect_module:nnn {widows-and-orphans}{warning}{error},check / info
.code:n = \msg_redirect_module:nnn {widows-and-orphans}{warning}{info},check / none
.code:n = \msg_redirect_module:nnn {widows-and-orphans}{warning}{none},check / warning.code:n = \msg_redirect_module:nnn {widows-and-orphans}{warning}{ }The other options set parameters to some hopefully “reasonable” values — no realsurprises here. LATEX internally uses \@clubpenalty so we need to set this too, if wechange \clubpenalty.
163
164
165
166
167
168
169
170
171
172
173
174
175
176

,orphans .choice:,orphans / prevent .code:n = \int_set:Nn \clubpenalty { 10000}\int_set:Nn \@clubpenalty { \clubpenalty },orphans / avoid.code:n = \int_set:Nn \clubpenalty { 5000}
\int_set:Nn \@clubpenalty { \clubpenalty },orphans / default .code:n = \int_set:Nn \clubpenalty {150}\int_set:Nn \@clubpenalty { \clubpenalty },widows .choice:,widows / prevent,widows / avoid,widows / default.code:n = \int_set:Nn \widowpenalty.code:n = \int_set:Nn \widowpenalty.code:n = \int_set:Nn \widowpenalty{ 10000{ 5000
{
150

}
}
}

,hyphens .choice:,hyphens / prevent .code:n = \int_set:Nn \brokenpenalty { 10000,hyphens / avoid.code:n = \int_set:Nn \brokenpenalty { 2000}}

The widows-and-orphans package

262

TUGboat, Volume 39 (2018), No. 3,hyphens / default .code:n = \int_set:Nn \brokenpenalty {50},prevent-all .code:n = \int_set:Nn \clubpenalty{ 10000}\int_set:Nn \widowpenalty{ 10000}\int_set:Nn \displaywidowpenalty{ 10000}\int_set:Nn \brokenpenalty{ 10000}\int_set:Nn \predisplaypenalty { 10000}\int_set:Nn \@clubpenalty { \clubpenalty }

177
178
179
180
181
182
183

As an exception, avoid-all doesn’t set \predisplaypenalty; maybe it should.
,avoid-all

184
185
186
187
188

%

189

.code:n = \int_set:Nn \clubpenalty{ 5000}\int_set:Nn \widowpenalty{ 5000}\int_set:Nn \displaywidowpenalty { 2000}
\int_set:Nn \brokenpenalty{ 2000}\int_set:Nn \predisplaypenalty{ 9999}\int_set:Nn \@clubpenalty { \clubpenalty }default-all reverts back to the standard LATEX default values:,default-all

190
191
192
193
194
195
196

.code:n = \int_set:Nn \clubpenalty{ 150}\int_set:Nn \widowpenalty{ 150}\int_set:Nn \displaywidowpenalty { 50}\int_set:Nn \brokenpenalty{ 100}\int_set:Nn \predisplaypenalty { 10000}\int_set:Nn \@clubpenalty { \clubpenalty }}Once declared we evaluate the options given to the package:
197\ProcessKeysPackageOptions{fmwao}

2.5

Document-level commands

Finally we declare the user-level commands:\WaOsetupThis runs the key setup on the first argument and then reinitializes the parameter
setup:
198
199\NewDocumentCommand\WaOsetup{m}{ \keys_set:nn{fmwao}{#1} \__fmwao_initialize: \ignorespaces }(End definition for \WaOsetup. This function is documented on page 253.)\WaOparameters

This parameterless command outputs a display of the current parameter settings.
200\NewDocumentCommand\WaOparameters{}{\prop_show:N \l__fmwao_penalties_prop}(End definition for \WaOparameters. This function is documented on page 253.)\WaOignorenext

And here is the command that suppresses any warning on the current page or column:
201
202\NewDocumentCommand\WaOignorenext{}{ \bool_gset_false:N \g__fmwao_gen_warn_bool }(End definition for \WaOignorenext. This function is documented on page 253.)

References[1] Frank Mittelbach. Managing forlorn paragraph lines (a.k.a. widows and orphans) in LATEX.
TUGboat 39:3, 246–251, 2018.
[2] LATEX3 Project Team. A collection of articles on expl3.
https://latex-project.org/publications/indexbytopic/l3-expl3/
 Frank Mittelbach
Mainz, Germany
https://www.latex-project.org
https://ctan.org/pkg/widows-and-orphansFrank Mittelbach