% \iffalse meta-comment %% %% File: widows-and-orphans.dtx (C) Copyright 2017-2023 Frank Mittelbach % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % https://www.latex-project.org/lppl.txt % % % The development version of the bundle can be found at % % https://github.com/FrankMittelbach/fmitex/widows-and-orphans % % for those people who are interested. % \fi % \iffalse %<*driver> \RequirePackage[nohyphen]{underscore} % no hyphen after undercore % in csname \documentclass [final] {l3doc-TUB} \setcounter{page}{1} % fix for _TF undefined, % https://github.com/latex3/latex3/issues/477#issuecomment-419458783 \ExplSyntaxOn \cs_set_protected:Npn \__codedoc_typeset_TF: { \group_begin: \exp_args:No \__codedoc_if_macro_internal:nT \l__codedoc_tmpa_tl { \color[gray]{0.5} } \itshape TF \iffalse \makebox[0pt][r] { \color{red} \underline{\phantom{\itshape TF} \kern-0.1em} } \fi \group_end: } \NewDocumentCommand \TypesetImplementationTF { m m } { \bool_if:NTF \g__codedoc_typeset_implementation_bool { #1 } { #2 } } \ExplSyntaxOff \makeatletter % fix for ltugboat issue with doc (if old doc is used) \def\dotfill{\leaders\hbox to.6em{\hss .\hss}\hskip\z@ plus 1fill\kern\z@}% \def\dotfil{\leaders\hbox to.6em{\hss .\hss}\hfil\kern\z@}% \def\pfill{\unskip~\dotfill\penalty500\strut\nobreak \dotfil~\ignorespaces}% \makeatother \begin{document} \DocInput{widows-and-orphans.dtx} \advance\signaturewidth by 42pt \makesignature \end{document} % % \fi % % \DoNotIndex{\cs_new:Npn,\cs_new_nopar:Npn,\csname,\endcsname,\fi:,\else:} % \DoNotIndex{\tl_new:N,\bool_new:N} % \DoNotIndex{\if_meaning:w,\iftrue,\int_new:N,\int_set:Nn} % \DoNotIndex{\prop_new:N,\tl_put_left:Nn} % \DoNotIndex{\bool_do_until:Nn,\bool_gset_false:N,\bool_gset_true:N} % \DoNotIndex{\bool_if:NTF,\bool_set_false:N,\bool_set_true:N} % \DoNotIndex{\clist_map_break:n,\clist_map_inline:nn} % \DoNotIndex{\cs_if_eq:NNTF,\exp_args:Nc} % \DoNotIndex{\int_case:nnTF,\int_case:nnF,\int_case:nnF,\int_incr:N} % \DoNotIndex{\int_to_arabic:n,\int_to_roman:n} % \DoNotIndex{\keys_define:nn,\keys_set:nn} % \DoNotIndex{\msg_err:nn,\msg_new:nnnn,\msg_redirect_module:nnn} % \DoNotIndex{\msg_warning:nn,\msg_warning:nnn,\msg_error:nn} % \DoNotIndex{\prop_clear:N,\prop_get:NnNTF,\prop_put:Nnn,\prop_show:N} % \DoNotIndex{\prg_new_conditional:Npnn} % \DoNotIndex{\prg_return_false:,\prg_return_true:} % \DoNotIndex{\prop_get:NVNT,\prop_put:NVn,\bool_if:NF,\bool_if:NT} % \DoNotIndex{\marginpar,\space,\ignorespaces,\thepage} % ^^A\DoNotIndex{\RequirePackage,\NeedsTeXFormat,\NewDocumentCommand} % % \title{The \pkg{widows-and-orphans} package} % \author{Frank Mittelbach} % \date{Printed \today\thanks{For the package version % see the \texttt{\string\ProvidesExplPackage} line in the code.}} % \address{Mainz, Germany} % \netaddress{https://www.latex-project.org} % \personalURL{https://ctan.org/pkg/widows-and-orphans} % % \maketitle % % \begin{abstract} % The \pkg{widows-and-orphans} package checks page or column breaks % for issues with widow or orphan 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\Dash if they are allowed by the % document parameter settings, which by default isn't the case. % % A general discussion of the problem of widows and orphans and % suggestions for resolution is given in~\cite{tub-wao}. % \end{abstract} % % \tableofcontents % % \newcommand\option[1]{\texttt{#1}} % \newcommand\ovalue[1]{\texttt{#1}} % % \section{Overview} % % To determine if a widow or orphan has occurred at a column or page % break, the package analyzes the \cs{outputpenalty} that triggered the % break. As \TeX{} adds special penalties to widow and orphan lines % (\cs{widowpenalty} and \cs{clubpenalty}), we can hope to identify % them, provided the penalties have unique values so that we don't end % up with false positives. % % \TypesetImplementationTF {\enlargethispage*{2\baselineskip}}{} % % 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 \cs{outputpenalty} has a value that matches one of the % problematic cases, issue a warning. % % Besides widows and orphans it is possible to detect other cases % controlled through penalty parameters, e.g., \cs{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 the end of a % column and issue a warning there too. % % We also do this for \cs{predisplaypenalty}, which controls a break just % in front of a math display. This is normally set to \texttt{10000} % so such breaks don't happen in standard \LaTeX{}, but if the value % is lowered it becomes possible, and thus a possible issue. % % \subsection{Options} % % The package has a number of key/value options to adjust its % behavior. % The option \option{check} defines what happens % when an issue is found: default is \ovalue{warning}, other % possibilities are \ovalue{error}, \ovalue{info} and \ovalue{none}. % % If the option \option{draft} is given on the document class then the % checking messages are reduced to \ovalue{info} messages and only appear in % the log file. If one wants warnings or errors in that case, one has % to explicitly specify \option{check} with the appropriate value on % the package level. % % The options \option{orphans} and \option{widows} set % reasonable parameter values; the default is to use whatever the class % defines. Possible values are \ovalue{prevent}, \ovalue{avoid} or % \ovalue{default}, the latter meaning use standard \LaTeX{} defaults. % % To set all parameters in one go you can use \option{prevent-all}, % \option{avoid-all} or \option{default-all}. These options also assign % values to \cs{brokenpenalty} and \cs{predisplaypenalty}. % % ^^A\TypesetImplementationTF {\pagebreak}{} % % % \subsection{User commands} % % The package provides three user-level commands. % % \vspace*{-4pt} % \begin{function}{\WaOsetup} % \begin{syntax} % \cs{WaOsetup} \meta{comma list} % \end{syntax} % This command accepts any of the package options and allows % adjusting the package behavior in mid-document if necessary. % \end{function} % \vspace*{-4pt} % \begin{function}{\WaOparameters} % \begin{syntax} % \cs{WaOparameters} % \end{syntax} % This command produces a listing of the parameter combinations and % their values. % \end{function} % \vspace*{-4pt} % \begin{function}{\WaOignorenext} % \begin{syntax} % \cs{WaOignorenext} % \end{syntax} % This command directs the package to not generate warnings for % the current page or columns (if we know that they can't be corrected). % \end{function} % % % % \subsection{Related packages} % % Package \pkg{nowidow}: This package offers some commands to help % pushing lines from one page to another by locally % requesting no widows or orphans\Dash possibly for several lines. In that % respect it implements one of the possibilities discussed in the % \TUB\ article~\cite{tub-wao}. 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. % % % % \StopEventually{ % \begin{thebibliography}{1} % \bibitem{tub-wao} % Frank Mittelbach. % \newblock Managing forlorn paragraph lines (a.k.a.\ widows and orphans) % in \LaTeX{}. % \newblock \textsl{TUGboat} 39:3, % \ifx\thisissuepageref\undefined \else \thisissuepageref{mitt-widows} ,\fi % 2018. % \bibitem{expl3} % \LaTeX3 Project Team. % \newblock A collection of articles on \pkg{expl3}.\\ % \url{https://latex-project.org/publications/indexbytopic/l3-expl3/} % \end{thebibliography} % \ifx\thisissuepageref\undefined ^^A is this TUB production ??? if not gen index % \setlength\IndexMin{200pt} \PrintIndex % \fi % } % % % \section{The implementation} % % The package is implemented in \pkg{expl3}, so the first thing we do % is to define a prefix for our internal commands, so that we can % write \texttt{@@} in the source when we mean the prefix \texttt{__fmwao}, % indicating that something is internal.\footnote{\texttt{l3docstrip} % expands that for us, so in the \texttt{.sty} file we get the % longer names with double underscores even though the real source % just contains \texttt{@@} all over the place. The same is done by % the \texttt{l3doc} class for the printed documentation you are % currently reading.\raggedright} % % \begin{macrocode} %<@@=fmwao> % \end{macrocode} % % Then we check that we are running on top of \LaTeXe{} and use a fairly recent % version of the kernel that contains the L3 programming layer, % \pkg{xparse} and the command \cs{ProcessKeyOptions}. For the latter % we need June 2022. % \changes{v1.0f}{2023/04/02}{Require a kernel of 2022-06 or newer} % \begin{macrocode} \NeedsTeXFormat{LaTeX2e}[2022-06-01] % \end{macrocode} % % Then we announce the package to the world at large. This declaration % will also tell \LaTeX{} that this is an \pkg{expl3} package and that % from now on the \pkg{expl3} conventions are to be obeyed, e.g., % \texttt{_} and \texttt{:} can appear in command names and whitespace % is automatically ignored (so no need for \texttt{\%} all over % the place).\footnote{Figure~\ref{fig:expl3} gives a short % introduction to the naming conventions of \pkg{expl3} for those % readers who haven't seen any code written in \pkg{expl3}. % For more details refer to~\cite{expl3}.} % % \begin{figure} % \centering % \setlength\fboxsep{7pt} % \noindent\hspace{-\marginparwidth}\fbox{\begin{minipage}{1.21\textwidth} % Commands in \pkg{expl3} use the following naming convention: % \begin{tabbing} % \qquad \texttt{\space\space\bslash\meta{module}\_\meta{action}:\meta{arg-spec}} % \quad % \= \% externally available command \\ % \qquad \texttt{\bslash\_\_\meta{module}\_\meta{action}:\meta{arg-spec}} % \> \% internal command local to the package % \end{tabbing} % % \vspace*{2pt} % % \begin{description} \raggedright % \item[\ttfamily\meta{module}] describes the (main) area to which % the command belongs, % \item[\ttfamily\meta{action}] describes the (main) action of the % command, and % \item[\ttfamily\meta{arg-spec}] shows the expected command % arguments and their preprocessing:\\ % \texttt{N} means expect a % single token; \\ % \texttt{n} means expect a (normal) braced % argument;\\ % \texttt{T} and \texttt{F} also represent braced % arguments, indicating ``true'' and ``false'' branches in a % conditional; \\ % \texttt{V} means expect a single token, interpret it as a % variable and pass its value on to the command;\\ % Finally, \texttt{p} stands for a (possibly empty) parameter spec, e.g., % \texttt{\#1\#2...}\ in a definition. % \\There are a number of other % argument types, but they aren't used in the code described here. % \end{description} % % \vspace*{2pt} % % Examples: % \begin{description} \raggedright % \item[\cs{cs_if_eq:NNTF}] is a conditional from the module % \texttt{cs} (command names) implementing the action \\ % \texttt{if\_eq} (if equal) and expecting two single tokens % (commands to compare) and a true and a false branch (one of % which is executed). % % \item[\cs{tl_put_left:Nn}] is a function from the module % \texttt{tl} (token lists) implementing \texttt{put\_left} and \\ % expecting a single token (a token list variable) and a braced % argument (the data to insert at \\ % the front/left in the variable). % \end{description} % % \medskip % % Variables start with \cs{l_} (for local) or \cs{g_} (for global) % and have the data type as the last part of the name. Variables % internal to the package use two underscores, e.g., % \cs{l__@@_gen_warn_bool}. % % \end{minipage}} % % \caption{Crash course in \pkg{expl3} command name conventions} % \label{fig:expl3} % \end{figure} % % \begin{macrocode} \ProvidesExplPackage{widows-and-orphans}{2023/04/02}{1.0f} {Detecting widows and orphans (FMi)} % \end{macrocode} % % % \begin{macro}[int]{\@makecol} % % As mentioned in the introduction we want to check the value of % \cs{outputpenalty} inside the output routine, and so we need % to add some code to the output routine. % % We add it to the front of \cs{@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 \cs[no-index]{marginpar}. % \begin{macrocode} \tl_put_left:Nn \@makecol { \@@_test_for_widows_etc: } % \end{macrocode} % \end{macro} % % % \subsection{Checking \cs{outputpenalty}} % % % \begin{macro}{\g_@@_gen_warn_bool} % To be able to suppress checking we define a global boolean % variable which by default is set to \texttt{true} (warnings enabled). % \begin{macrocode} \bool_new:N \g_@@_gen_warn_bool \bool_gset_true:N \g_@@_gen_warn_bool % \end{macrocode} % \end{macro} % % % % \begin{macro}{\@@_test_for_widows_etc:} % % What are the different values related to orphans and widows and % the like that can appear in \cs{outputpenalty}? Here is the % basic list: % \begin{description} % % \item[$\cs{widowpenalty} + \cs{interlinepenalty} \to$] if the % break happens on the second-last line of a paragraph and the % paragraph is not followed by a math display. % % \item[$\cs{displaywidowpenalty} + \cs{interlinepenalty} \to$] if % the break happens on the second-last line of a paragraph and the % paragraph is followed by a math display. % % \item[$\cs{clubpenalty} + \cs{interlinepenalty} \to$] if the % break happens after the first line of a paragraph and the % paragraph has more than two lines. % % \item[$\cs{clubpenalty}+\cs{widowpenalty} + \cs{interlinepenalty} % \to$] if the break happens after the first line of a paragraph in % a two-line paragraph (thus this line is also the second-last % line). % % \item[$\cs{clubpenalty}+\cs{displaywidowpenalty} + % \cs{interlinepenalty} \to $] if the % break happens after the first line of a paragraph in a two-line % paragraph and a math display follows. % % \end{description} % That's it for widows and orphans. If we also consider hyphenated % breaks then we get a further set of cases, namely all of the % above with \cs{brokenpenalty} added in and the case of % \cs{brokenpenalty} on its own (with just \cs{interlinepenalty} added). % % \begin{macrocode} \cs_new:Npn \@@_test_for_widows_etc: { % \end{macrocode} % So here is the main test. We compare \cs{outputpenalty} with each % case until we have a hit or run out of cases. Instead of adding % \cs{interlinepenalty} to each case we subtract it once from % \cs{outputpenalty} to make the comparison a little more % efficient. % % If we get a hit, we execute the corresponding code: either % \cs{@@_problem_identified:n} or \cs{@@_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 % ``\texttt{Widow}'' while in the second case it is also a % ``widow'' problem but the text will say ``\texttt{Display\string~ % widow}''.\footnote{If you haven't seen much \pkg{expl3} code you % may wonder about the \texttt{\string~}. As the code ignores % spaces we have to mark up real spaces and for this the tilde is % used. In \pkg{expl3} code this does not act as a tie, but simply % as a catcode 10 space character (while normal spaces are % ignored).} This just saves a bit of space when different cases % share more or less the same message text. % \begin{macrocode} \int_case:nnF { \outputpenalty - \interlinepenalty } { { \widowpenalty } { \@@_problem_identified:nn{widow}{Widow} } { \displaywidowpenalty } { \@@_problem_identified:nn{widow}{Display~ widow} } % \end{macrocode} % Above we have always talked about testing for \cs{clubpenalty} % because that is the register \TeX{} is using. However, \LaTeX{} % changes its value back and forth between \texttt{10000} (after a % heading) and \cs{@clubpenalty} (the saved value) and sometimes % even \texttt{0}. Thus when the output routine is triggered it % could be any of these values and in case it is zero we would get % spurious matches. So for comparison we always test against % \cs{@clubpenalty}. % \begin{macrocode} { \@clubpenalty } { \@@_problem_identified:n{orphan} } { \@clubpenalty + \widowpenalty } { \@@_problem_identified:nn{orphan-widow}{} } { \@clubpenalty + \displaywidowpenalty } { \@@_problem_identified:nn{orphan-widow}{display} } % \end{macrocode} % % A similar issue comes from a hyphen at the end of a column or % page in which case \TeX{} adds \cs{brokenpenalty} so we can test % against that: % \begin{macrocode} { \brokenpenalty } { \@@_problem_identified:n{hyphen} } % \end{macrocode} % However, I said ``\TeX{} adds'', which means if a widow line also % ends in a hyphen then the penalty will be the sum of both % individual penalties. So all the cases above need to be repeated % with \cs{brokenpenalty} added to the value. % We generate the same warnings, though\Dash e.g., we will say ``Widow % detected'' and not ``Hyphenated widow line detected'' as the % latter seems to be overkill. % \begin{macrocode} { \brokenpenalty + \widowpenalty } { \@@_problem_identified:nn{widow}{Widow} } { \brokenpenalty + \displaywidowpenalty } { \@@_problem_identified:nn{widow}{Display~ widow} } { \brokenpenalty + \@clubpenalty } { \@@_problem_identified:n{orphan} } { \brokenpenalty + \@clubpenalty + \widowpenalty } { \@@_problem_identified:nn{orphan-widow}{} } { \brokenpenalty + \@clubpenalty + \displaywidowpenalty } { \@@_problem_identified:nn{orphan-widow}{display} } % \end{macrocode} % Finally there is \cs{predisplaypenalty} that we may as well check also % (in case it was set to a value lower than \texttt{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 % ``\texttt{Lonely\string~ display}'' in the second argument. % This case does not have \cs{interlinepenalty} added by % \TeX{}, so we have to undo the optimization above. % \begin{macrocode} { \predisplaypenalty - \interlinepenalty } { \@@_problem_identified:nn{widow}{Lonely~ display} } } % \end{macrocode} % The last argument of \cs[no-index]{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 \cs{g_@@_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 problems on 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. % % \begin{macrocode} { \bool_if:NF \g_@@_gen_warn_bool { \msg_error:nn{widows-and-orphans}{no-problem} } } % \end{macrocode} % Finally, we make sure that the next page or column is again % checked. % \begin{macrocode} \bool_gset_true:N \g_@@_gen_warn_bool } % \end{macrocode} % \end{macro} % \begin{macro}{\@@_problem_identified:n} % \begin{macro}{\@@_problem_identified:nn} % These commands prepare for generating a warning, but only if we % are supposed to, i.e., if \cs{g_@@_gen_warn_bool} is true. % \begin{macrocode} \cs_new:Npn \@@_problem_identified:n #1 { \bool_if:NT \g_@@_gen_warn_bool { \msg_warning:nn{widows-and-orphans}{#1} } } \cs_new:Npn \@@_problem_identified:nn #1 #2 { \bool_if:NT \g_@@_gen_warn_bool { \msg_warning:nnn{widows-and-orphans}{#1}{#2} } } % \end{macrocode} % \end{macro} % \end{macro} % \subsection{Messages to the user} % % % \begin{macro}{\@@_this_page:} % \begin{macro}{\@@_next_page:} % For displaying nice messages to the user we need a few helper % commands. The two here show the page number of the current or % next page. They are semi-smart, that is they 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). % \begin{macrocode} \cs_new:Npn \@@_this_page: { \@@_some_page:n \c@page } \cs_new:Npn \@@_next_page: { \@@_some_page:n { \c@page + 1 } } % \end{macrocode} % \end{macro} % \end{macro} % \begin{macro}{\@@_some_page:n} % \begin{macro}{\@@_roman_thepage:} % This macro first compares \cs[no-index]{thepage} against the code that % would be used in the case of a roman numeral representation, and then % displays its argument using either arabic numbers or roman numerals. % \begin{macrocode} \cs_new:Npn \@@_some_page:n #1 { \cs_if_eq:NNTF \thepage \@@_roman_thepage: { \int_to_roman:n } { \int_to_arabic:n } { #1 } } % \end{macrocode} % \cs{@@_roman_thepage:} just stores the default definition of % \cs[no-index]{thepage} if page numbers are represented by roman numerals % for use in the comparison above. % \begin{macrocode} \cs_new_nopar:Npn \@@_roman_thepage: {\csname @roman\endcsname \c@page} % \end{macrocode} % \end{macro} % \end{macro} % \begin{macro}[TF, int]{\legacy_switch_if:n} % To evaluate \LaTeXe{} boolean switches in a nice way, we need a % conditional. Eventually this will probably make it into the \pkg{expl3} % code in this or a similar form, but right now it is missing. % \begin{macrocode} \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: \else: \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % % % % The first message is issued if we have been directed to ignore a % problem and there wasn't one:{\hfuzz=14.1pt % \begin{macrocode} \msg_new:nnnn {widows-and-orphans} {no-problem} { No~ problem~ to~ suppress~ on~ this~ page! } { Suppression~ of~ a~ widow~ or~ orphan~ problem~ was~ requested~ but~ on~ the~ current~ page~ there~ doesn't~ seem~ to~ be~ any.~ Maybe~ the~ text~ was~ changed~ and~ the~ request~ should~ get~ (re)moved?} % \end{macrocode} % } % % \bigskip % % % % % The next message is about orphans. They can appear at the bottom of % the first or the second column of the current page, if we are in % two-column mode. So we check for this and adjust the message accordingly. % \begin{macrocode} \msg_new:nnnn {widows-and-orphans} {orphan} { Orphan~ on~ page~ \@@_this_page: \legacy_switch_if:nT {@twocolumn} { \space ( \legacy_switch_if:nTF {@firstcolumn} { first~ } { second~ } column) } } { Check~ out~ the~ page~ and~ see~ if~ you~ can~ avoid~ the~ orphan.} % \end{macrocode} % % % \bigskip % % % 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. % \begin{macrocode} \msg_new:nnnn {widows-and-orphans} {hyphen} { Hyphen~ in~ last~ line~ of~ page~ \@@_this_page: \legacy_switch_if:nT {@twocolumn} { \space ( \legacy_switch_if:nTF {@firstcolumn} { first~ } { second~ } column) } } { Check~ out~ the~ page~ and~ see~ if~ you~ can~ get~ a~ better~ line~ break. } % \end{macrocode} % % % \bigskip % % % Widows need a different logic since we detect them when cutting a % previous page or column 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: % \begin{macrocode} \msg_new:nnnn {widows-and-orphans} {widow} { #1~ on~ page~ \legacy_switch_if:nTF {@twocolumn} { \legacy_switch_if:nTF {@firstcolumn} { \@@_this_page: \space (second~ } { \@@_next_page: \space (first~ } column) } { \@@_next_page: } } { Check~ out~ the~ page~ and~ see~ if~ you~ can~ avoid~ the~ widow.} % \end{macrocode} % % % \bigskip % % % The case of both widow and orphan is similar, but we obviously % need different text so we made it its own message. % \begin{macrocode} \msg_new:nnnn {widows-and-orphans} {orphan-widow} { Orphan~ \legacy_switch_if:nTF {@twocolumn} { \legacy_switch_if:nTF {@firstcolumn} { and~ #1 widow~ on~ page~ \@@_this_page: \space (first~ and~ second~ } { on~ page~ \@@_this_page: \space (second~ column)~ and~ #1 widow~ on~ page~ \@@_next_page: \space (first~ } } { on~ page~ \@@_this_page: \space (second~ column)~ and~ #1 widow~ on~ page~ \@@_next_page: \space (first~ } column) } { Check~ out~ the~ page~ and~ see~ if~ you~ can~ avoid~ both~ orphan~ and~ widow.} % \end{macrocode} % % % % % % % \subsection{Adjusting parameter values} % % To avoid (a lot of) false positives during checking it is important % that the parameter values are chosen in a way that all possible % combinations lead to unique \cs{outputpenalty} values. At the same % time, we want them to be as close as possible to the values that % have 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: % \begin{itemize} % \item We set up a property list to hold penalty values that % can appear in \cs{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 % \texttt{150} and the value \cs{widowpenalty}. % % \item Initially the property list is empty. So adding the first item % simply means taking the value of one parameter, say \texttt{150} % from $\cs{widowpenalty}+\cs{interlinepenalty}$, as the key and % this formula as the property list value. % % \item For the next parameter, say \cs{@clubpenalty}, we check if its % value (or more precisely its value plus \cs{interlinepenalty}) is % already a key in the property list. If that is the case, then we % have failed and must modify the parameter value somehow. % % \item 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., \cs{@clubpenalty} (new) and % \cs{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. % % \item 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. % % \item We then change the parameter to this value and add all the % combinations we tried before to the property list (that is % $\cs{@clubpenalty}+\cs{interlinepenalty}$ both alone and together with % \cs{widowpenalty} in our example). Thus from now on those are % also forbidden values. % % \item We do all this with a helper command that takes the new % parameter as the first argument and the list of different cases % to try as a comma-separated list as a second argument, e.g., %\begin{verbatim} % \__fmwao_decide_penalty:Nn \@clubpenalty % { \@clubpenalty + \interlinepenalty , % \@clubpenalty + \widowpenalty + \interlinepenalty } %\end{verbatim} % % \item This way we are adding all relevant parameters to the property % list and at the same time adjusting their values if needed. % \item Once all parameters are handled the property list is no longer % needed as the parameters got changed along the way, but we keep it % around as it allows for a simple display of all settings in one go. % \end{itemize} % % % \begin{macro}{\l_@@_penalties_prop} % Here is the property list for our process. % \begin{macrocode} \prop_new:N \l_@@_penalties_prop % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_initialize:} % Now we are ready to go. The first action is to clear the property % list as the initialization may happen several times. % \begin{macrocode} \cs_new:Npn \@@_initialize: { \prop_clear:N \l_@@_penalties_prop % \end{macrocode} % When \TeX{} breaks a page at a glue item with no explicit penalty involved % it sets \cs{outputpenalty} to \texttt{10000} in the output % routine to distinguish it from a case where an explicit penalty of % \texttt{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. % \begin{macrocode} \prop_put:Nnn \l_@@_penalties_prop {10000} {break~ at~ glue} % \end{macrocode} % The next thing is to add the values for \cs{@lowpenalty}, % \cs{@medpenalty}, \cs{@highpenalty} to the property list as % they also may show up in \cs{outputpenalty} if a user says, for % example, \verb=\nopagebreak[2]=. % % Such a penalty from an explicit page break request does not get % \cs{interlinepenalty} added in. % \begin{macrocode} \@@_decide_penalty:Nn \@lowpenalty { \@lowpenalty} \@@_decide_penalty:Nn \@medpenalty { \@medpenalty} \@@_decide_penalty:Nn \@highpenalty { \@highpenalty} % \end{macrocode} % Then comes the first real parameter for the orphans: % \begin{macrocode} \@@_decide_penalty:Nn \@clubpenalty { \@clubpenalty + \interlinepenalty } % \end{macrocode} % followed by the one for the widows and the one for the display widows: % \begin{macrocode} \@@_decide_penalty:Nn \widowpenalty { \widowpenalty + \interlinepenalty , \widowpenalty + \@clubpenalty + \interlinepenalty } \@@_decide_penalty:Nn \displaywidowpenalty { \displaywidowpenalty + \interlinepenalty , \displaywidowpenalty + \@clubpenalty + \interlinepenalty } % \end{macrocode} % \cs{brokenpenalty} can appear on its own, and also with each and % every combination we have seen so far: % \begin{macrocode} \@@_decide_penalty:Nn \brokenpenalty { \brokenpenalty + \interlinepenalty , \brokenpenalty + \@clubpenalty + \interlinepenalty , \brokenpenalty + \widowpenalty + \interlinepenalty , \brokenpenalty + \widowpenalty + \@clubpenalty + \interlinepenalty , \brokenpenalty + \displaywidowpenalty + \@clubpenalty + \interlinepenalty } % \end{macrocode} % Finally we have the parameter for lonely displays (again without % \cs{interlinepenalty} being added): % \begin{macrocode} \@@_decide_penalty:Nn \predisplaypenalty { \predisplaypenalty } } % \end{macrocode} % If we run the above code with \LaTeX's default parameter settings % in force it will make a few adjustments and the property list % will afterwards contain % the following entries: %\begin{small} %\begin{verbatim} % The property list \l__fmwao_penalties_prop contains the pairs (without % outer 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{verbatim} %\end{small} % \end{macro} % % % \begin{macro}{\l_@@_tmp_int} % \begin{macro}{\l_@@_tmp_tl} % \begin{macro}{\l_@@_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. % \begin{macrocode} \int_new:N \l_@@_tmp_int \tl_new:N \l_@@_tmp_tl \bool_new:N \l_@@_success_bool % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % % \begin{macro}{\@@_decide_penalty:Nn} % This is the core command that does the real work of choosing values. % Let's recall that its 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. % \begin{macrocode} \cs_new:Npn \@@_decide_penalty:Nn #1 #2 { % \end{macrocode} % We start by setting the boolean to \texttt{false} and then run a % loop until we have found a suitable parameter value that meets % our criteria. % \begin{macrocode} \bool_set_false:N \l_@@_success_bool \bool_do_until:Nn \l_@@_success_bool % \end{macrocode} % Inside the loop we start with the assumption that the current % value of the parameter is fine 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. % \begin{macrocode} { \bool_set_true:N \l_@@_success_bool % \end{macrocode} % For the verification we try each item in the second parameter to % see if that is already in the property list. % This means evaluating the expression to get the penalty value % and then 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. % \begin{macrocode} \clist_map_inline:nn { #2 } { \int_set:Nn \l_@@_tmp_int {##1} \prop_get:NVNT \l_@@_penalties_prop \l_@@_tmp_int \l_@@_tmp_tl { \clist_map_break:n {\bool_set_false:N\l_@@_success_bool} } } % \end{macrocode} % Once we have finished, the boolean will tell us if we are % successful so far. If yes, it means there was no conflict. We % therefore add all combinations with this parameter to the % property list, as from now on they are forbidden as well. % % % So we % map once more over the second argument and enter them: % \begin{macrocode} \bool_if:NTF \l_@@_success_bool { \clist_map_inline:nn { #2 } { \int_set:Nn \l_@@_tmp_int {##1} \prop_put:NVn \l_@@_penalties_prop \l_@@_tmp_int {##1} } } % \end{macrocode} % If we failed we increment the parameter value and retry: % \begin{macrocode} { \int_incr:N #1 } } } % \end{macrocode} % % % One place where we will run this code is at the beginning of the % document (so that changes 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 \cs{WaOsetup}. % \begin{macrocode} \AtBeginDocument { \@@_initialize: } % \end{macrocode} % % \end{macro} % % \subsection{The option setup} % % The options are fairly straightforward: % % \begin{macrocode} \keys_define:nn {fmwao} { % \end{macrocode} % By default messages are given as warnings above. If anything else % is wanted the option \option{check} can be used which simply % changes the message class used internally: % \begin{macrocode} ,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}{ } % \end{macrocode} % If the class option \texttt{draft} was given we downgrade the % checks to info, thus if one wants warnings or errors even then, % then one has to give the check key explicitly on package level. % \changes{v1.0f}{2023/04/02}{Honor a global draft option (gh/1)} % \begin{macrocode} ,draft .meta:n = {check = info} % \end{macrocode} % % The other options set parameters to some hopefully ``reasonable'' % values\Dash no real surprises here. \LaTeX{} internally uses % \cs{@clubpenalty} so we need to set this too, if we change % \cs{clubpenalty}. % \begin{macrocode} ,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 } % \end{macrocode} % % \begin{macrocode} ,widows .choice: ,widows / prevent .code:n = \int_set:Nn \widowpenalty { 10000 } ,widows / avoid .code:n = \int_set:Nn \widowpenalty { 5000 } ,widows / default .code:n = \int_set:Nn \widowpenalty { 150 } % \end{macrocode} % % \begin{macrocode} ,hyphens .choice: ,hyphens / prevent .code:n = \int_set:Nn \brokenpenalty { 10000 } ,hyphens / avoid .code:n = \int_set:Nn \brokenpenalty { 2000 } ,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 } % \end{macrocode} % As an exception, \option{avoid-all} doesn't set % \cs{predisplaypenalty}; maybe it should. % \begin{macrocode} ,avoid-all .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 } % \end{macrocode} % \option{default-all} reverts back to the standard \LaTeX{} % default values: % \begin{macrocode} ,default-all .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 } } % \end{macrocode} % Once declared we evaluate the options given to the package: % \changes{v1.0f}{2023/04/02}{Use kernel method now not \pkg{l3keys2e}} % \begin{macrocode} \ProcessKeyOptions[fmwao] % \end{macrocode} % % % \subsection{Document-level commands} % % Finally we declare the user-level commands: % % \begin{macro}{\WaOsetup} % This runs the key setup on the first argument and then % reinitializes the parameter setup: % \begin{macrocode} \NewDocumentCommand\WaOsetup{m} { \keys_set:nn{fmwao}{#1} \@@_initialize: \ignorespaces } % \end{macrocode} % \end{macro} % % % \begin{macro}{\WaOparameters} % This parameterless command outputs a display of the % current parameter settings. % \begin{macrocode} \NewDocumentCommand\WaOparameters{}{\prop_show:N \l_@@_penalties_prop} % \end{macrocode} % \end{macro} % % % \begin{macro}{\WaOignorenext} % And here is the command that suppresses any warning on the % current page or column: % \begin{macrocode} \NewDocumentCommand\WaOignorenext{} { \bool_gset_false:N \g_@@_gen_warn_bool } % \end{macrocode} % \end{macro} % % \small\Finale % \endinput