summaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2022-03-09 03:01:03 +0000
committerNorbert Preining <norbert@preining.info>2022-03-09 03:01:03 +0000
commitb23d702a4a177c6d047cd03020e5a0fb7effd88a (patch)
tree60f860c1b86caf0fa6637b79a3490f642b57c761 /macros
parentb1f3f2681b2bf35570ad11f65270777f38c55f7e (diff)
CTAN sync 202203090301
Diffstat (limited to 'macros')
-rw-r--r--macros/latex/contrib/easybook/easybook.dtx96
-rw-r--r--macros/latex/contrib/easybook/easybook.pdfbin586978 -> 586959 bytes
-rw-r--r--macros/latex/contrib/easybook/easybook.tex6
-rw-r--r--macros/latex/contrib/footmisc/changes.txt5
-rw-r--r--macros/latex/contrib/footmisc/footmisc-code.pdfbin363832 -> 363788 bytes
-rw-r--r--macros/latex/contrib/footmisc/footmisc-doc.pdfbin249093 -> 249001 bytes
-rw-r--r--macros/latex/contrib/footmisc/footmisc.dtx29
-rw-r--r--macros/latex/contrib/robotarm/README.md3
-rw-r--r--macros/latex/contrib/robotarm/robotarm.dtx940
-rw-r--r--macros/latex/contrib/robotarm/robotarm.pdfbin0 -> 185467 bytes
-rw-r--r--macros/latex/contrib/robotarm/robotarm.sty375
-rw-r--r--macros/luatex/generic/lua-widow-control/README.md7
-rw-r--r--macros/luatex/generic/lua-widow-control/lua-widow-control-2022-02-22.sty94
-rw-r--r--macros/luatex/generic/lua-widow-control/lua-widow-control.lua390
-rw-r--r--macros/luatex/generic/lua-widow-control/lua-widow-control.opm9
-rw-r--r--macros/luatex/generic/lua-widow-control/lua-widow-control.pdfbin346952 -> 415431 bytes
-rw-r--r--macros/luatex/generic/lua-widow-control/lua-widow-control.sty262
-rw-r--r--macros/luatex/generic/lua-widow-control/lua-widow-control.tex9
-rw-r--r--macros/luatex/generic/lua-widow-control/lwc-documentation.mkxl5
-rw-r--r--macros/luatex/generic/lua-widow-control/lwc-documentation.tex250
-rw-r--r--macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkiv72
-rw-r--r--macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkxl72
22 files changed, 2299 insertions, 325 deletions
diff --git a/macros/latex/contrib/easybook/easybook.dtx b/macros/latex/contrib/easybook/easybook.dtx
index c7a2ae3560..54cc922175 100644
--- a/macros/latex/contrib/easybook/easybook.dtx
+++ b/macros/latex/contrib/easybook/easybook.dtx
@@ -15,7 +15,7 @@
%<*class>
\NeedsTeXFormat{LaTeX2e}[2020/10/01]
\RequirePackage{l3keys2e}
-\ProvidesExplClass{easybook}{2022/03/06}{1.641}
+\ProvidesExplClass{easybook}{2022/03/08}{1.642}
{Easily typesetting Chinese theses or books}
\bool_set_false:N \l__eb_compile_draft_bool
@@ -76,22 +76,22 @@
\cs_new_eq:NN \eb_switch_family:n \ctex_ltj_switch_family:n
}
\cs_new_protected:Npn \eb_setmainfont:nn #1#2
- { \__fontspec_main_setmainfont:nn {#1} {#2} }
+ { \__fontspec_main_setmainfont:nn {#2} {#1} }
\cs_new_protected:Npn \eb_setsansfont:nn #1#2
- { \__fontspec_main_setsansfont:nn {#1} {#2} }
+ { \__fontspec_main_setsansfont:nn {#2} {#1} }
\cs_new_protected:Npn \eb_setmonofont:nn #1#2
- { \__fontspec_main_setmonofont:nn {#1} {#2} }
+ { \__fontspec_main_setmonofont:nn {#2} {#1} }
\cs_new_protected:Npn \eb_setCJKmainfont:nn #1#2
- { \eb_set_family:nnn { \CJKrmdefault } {#1} {#2} }
+ { \eb_set_family:nnn { \CJKrmdefault } {#2} {#1} }
\cs_new_protected:Npn \eb_setCJKsansfont:nn #1#2
- { \eb_set_family:nnn { \CJKsfdefault } {#1} {#2} }
+ { \eb_set_family:nnn { \CJKsfdefault } {#2} {#1} }
\cs_new_protected:Npn \eb_setCJKmonofont:nn #1#2
- { \eb_set_family:nnn { \CJKttdefault } {#1} {#2} }
+ { \eb_set_family:nnn { \CJKttdefault } {#2} {#1} }
\eb_seq_map_inline:nn
{ main,sans,mono,CJKmain,CJKsans,CJKmono }
{
\exp_args:Nc \RenewDocumentCommand { set#1font }{O{}mO{}}
- {\use:c { eb_set#1font:nn } {##1,##3} {##2}}
+ {\use:c { eb_set#1font:nn } {##2} {##1,##3}}
}
\RenewDocumentCommand{\newCJKfontfamily}{omO{}mO{}}
{
@@ -303,7 +303,7 @@
%<*package>
\NeedsTeXFormat{LaTeX2e}[2020/10/01]
\RequirePackage{l3keys2e,etoolbox}
-\ProvidesExplPackage{easybase}{2022/03/06}{1.641}
+\ProvidesExplPackage{easybase}{2022/03/08}{1.642}
{Easily typesetting Chinese theses or books}
\cs_generate_variant:Nn \dim_set:Nn { NV }
@@ -451,7 +451,7 @@
{
style,subfont,spread,
refset,bibset,thmset,
- geoset,hdrset,tocset
+ geoset,hdrset,tocset,list
}
{
\ctex_define:n { #1 .meta:nn = { ctex/#1 } {##1} }
@@ -873,11 +873,10 @@
fnindent .dim_set:N = \footindent,
fnindent .initial:n = 0.8em,
fnparskip .skip_set:N = \footparskip,
- fnparskip .initial:n = 0ex,
+ fnparskip .initial:n = 0ex plus .1ex,
fnparindent .tl_set:N = \footparindent,
fnparindent .initial:n = 2em,
fnpara-form .tl_set:N = \l__eb_fnpara_format_tl,
-
fntext-pos .tl_set:N = \l__eb_fntext_position_tl,
fntext-pos .initial:n = super,
fntext-code .code:n = \eb_assign_fntext_code:n {#1},
@@ -885,20 +884,17 @@
fnmate-form .code:n = \eb_fnboth_format_set:nn {#1} { mate },
fncust-form .code:n = \eb_fnboth_format_set:nn {#1} { cust },
fnboth-form .meta:n = { fnmate-form = #1,fncust-form = #1 },
-
footnotetype .multichoices:nn =
{ default,hang,bottom,para,flush }
{ \eb_footnote_value_handle:n {#1} },
footnotetype .value_required:n = true,
footnotetype .initial:n = hang,
-
fnmark-vmove .code:n = \eb_fnmate_move_set:nnn {#1} { mark } { v },
fnmark-hmove .code:n = \eb_fnmate_move_set:nnn {#1} { mark } { h },
fntext-vmove .code:n = \eb_fnmate_move_set:nnn {#1} { text } { v },
fnmark-vmove .initial:n = 0pt,
fnmark-hmove .initial:n = 0pt,
fntext-vmove .initial:n = 0pt,
-
fnmark-num .choices:nn =
{ plain,pifont,pifont*,pifont-sans,pifont-sans* }
{ \eb_fnmate_normal_label_set:Nn \theupfootnote {#1} },
@@ -1261,7 +1257,7 @@
{
\ctex_define:n
{
- list/#1 .meta:n =
+ list/#1 .meta:n =
{
figure/#1 = ##1,table/#1 = ##1,
lstlisting/#1 = ##1
@@ -1273,7 +1269,7 @@
{
\ctex_define:nn { tocset }
{
- #1-every .code:n =
+ #1-every .code:n =
{
\ctex_set:n
{
@@ -1325,32 +1321,47 @@
{ \p@numberline{\use:c { #1name }~\use:c { the#1 }} }
\ctex_set:n
{
- part/tocformat = \addvspace{1pc}\sffamily\large,
- part/tocindent = 0em,
- part/tocrule = \tocrule{}[\bfseries],
- part/tocline = \CTEXnumberline{#1}#2,
- chapter/tocformat = \addvspace{1pc}\sffamily,
- chapter/tocindent = 0em,
- chapter/tocrule = \tocrule{$\cdot$}[\bfseries],
- chapter/tocline = \CTEXnumberline{#1}#2,
- section/tocformat = \bool_if:NF \l__eb_class_mode_book_bool { \sffamily },
- section/tocindent = \bool_if:NTF \l__eb_class_mode_book_bool { 1.5em } { 0em },
- section/tocrule =
+ part =
{
- \bool_if:NTF \l__eb_class_mode_book_bool
- { \tocrule{$\cdot$} }
- { \tocrule{$\cdot$}[\bfseries] }
+ tocformat = \addvspace{1pc}\sffamily\large,
+ tocindent = 0em,
+ tocrule = \tocrule{}[\bfseries],
+ tocline = \CTEXnumberline{#1}#2
+ },
+ chapter =
+ {
+ tocformat = \addvspace{1pc}\sffamily,
+ tocindent = 0em,
+ tocrule = \tocrule{$\cdot$}[\bfseries],
+ tocline = \CTEXnumberline{#1}#2
+ },
+ section =
+ {
+ tocformat = \bool_if:NF \l__eb_class_mode_book_bool { \sffamily },
+ tocindent = \bool_if:NTF \l__eb_class_mode_book_bool { 1.5em } { 0em },
+ tocrule =
+ {
+ \bool_if:NTF \l__eb_class_mode_book_bool
+ { \tocrule{$\cdot$} }
+ { \tocrule{$\cdot$}[\bfseries] }
+ }
+ },
+ subsection =
+ {
+ tocindent = \bool_if:NTF \l__eb_class_mode_book_bool { 3.8em } { 1.4em },
+ tocrule = \tocrule{$\cdot$}
+ },
+ list =
+ {
+ tocindent = 0em,
+ tocrule = \tocrule{$\cdot$},
+ between = \bool_if:NT \l__eb_class_mode_book_bool { \vspace*{-10pt} }
},
- subsection/tocindent = \bool_if:NTF \l__eb_class_mode_book_bool { 3.8em } { 1.4em },
- subsection/tocrule = \tocrule{$\cdot$},
- list/tocindent = 0em,
- list/tocrule = \tocrule{$\cdot$},
- list/between = \bool_if:NT \l__eb_class_mode_book_bool { \vspace*{-10pt} },
table/tocline = \listnumberline{table}#2,
figure/tocline = \listnumberline{figure}#2,
- lstlisting/tocline = \listnumberline{lstlisting}#2
+ lstlisting/tocline = \listnumberline{lstlisting}#2,
+ tocset/tocafter-every = \hspace{1em}
}
-\ctex_set:nn { tocset } { tocafter-every = \hspace{1em} }
\prop_new:N \l__eb_list_label_entry_prop
\prop_set_from_keyval:Nn \l__eb_list_label_entry_prop
{
@@ -1784,9 +1795,8 @@
\tl_gset:Nn \dblfloatpagefraction { 0.9 }
}
\dim_zero_new:N \mathindent
-\skip_set:Nn \parskip { 0pt plus 2pt minus 1pt }
-\skip_set:Nn \textfloatsep { 12pt plus 2pt minus 4pt }
-\skip_set:Nn \dbltextfloatsep { 12pt plus 2pt minus 4pt }
+\skip_set:Nn \textfloatsep { 12pt plus 4pt minus 3pt }
+\skip_set:Nn \dbltextfloatsep { 12pt plus 4pt minus 3pt }
\skip_set:Nn \multicolsep { 12pt plus 4pt minus 3pt }
%% \int_gset:Nn \vbadness { 10000 }
%% \int_gset:Nn \hbadness { 10000 }
@@ -2217,7 +2227,6 @@
linktype/colors .code:n = \eb_link_colors_set:,
linktype/various .meta:n = linktype/colors,
linktype .initial:n = edge,
-
linktoc .choice:,
linktoc/unknown .code:n = \eb_link_toc_put:n {#1},
linktoc .default:n = all,
@@ -2257,6 +2266,7 @@
urlcolor = ctex@url,urlbordercolor = ctex@url,
citecolor = ctex@cite,citebordercolor = ctex@cite
}
+ \hypersetup{bookmarksnumbered = true}
\exp_args:NV \hypersetup \l__eb_hyperref_clist
}
@@ -2307,7 +2317,7 @@
}
%</package>
%<*tcolorbox>
-\ProvidesExplFile{eb-tcolorbox.cfg}{2022/03/06}{1.641}
+\ProvidesExplFile{eb-tcolorbox.cfg}{2022/03/08}{1.642}
{Customization of tcolorbox for easybook}
\cs_set_protected:Npn \addtotcbstyle #1#2
diff --git a/macros/latex/contrib/easybook/easybook.pdf b/macros/latex/contrib/easybook/easybook.pdf
index db967b2641..e17b80e4c5 100644
--- a/macros/latex/contrib/easybook/easybook.pdf
+++ b/macros/latex/contrib/easybook/easybook.pdf
Binary files differ
diff --git a/macros/latex/contrib/easybook/easybook.tex b/macros/latex/contrib/easybook/easybook.tex
index 26b7f8986a..20fd6cbb52 100644
--- a/macros/latex/contrib/easybook/easybook.tex
+++ b/macros/latex/contrib/easybook/easybook.tex
@@ -21,8 +21,8 @@
name = easybook,
color-scheme = blue,
title = EASYBOOK使用手册,
- version = v1.641,
- date = 2022/03/06,
+ version = v1.642,
+ date = 2022/03/08,
authors = 瞿毅,
info = 简便地排版中文学位论文或书籍,
email = q1jx@foxmail.com,
@@ -531,7 +531,7 @@
\subsection{超链接}
\label{subsec:hyperlinks}
-模块 \module*{refset} 将超链接选项交给 \pkg*{hyperref} 宏包处理,除了一些关于 \code{pdf} 书签或已预设的选项可能失效,其它由接口命令 \cs*{hypersetup} 支持的多数选项可用。
+模块 \module*{refset} 将超链接选项交给 \pkg*{hyperref} 宏包处理。除了一些超链接颜色的选项失效,其它由宏包全局和接口命令 \cs*{hypersetup} 支持的多数选项可用。
\begin{options}
\keychoice{linktype}{edge,colors,none}\Module{refset}\Default{edge}
超链接类型。\code{edge} 使超链接形式为边框,\code{colors} 使超链接字体为彩色,\code{none} 关闭超链接边框与颜色,此时 \option{linkcolor} 选项无效。
diff --git a/macros/latex/contrib/footmisc/changes.txt b/macros/latex/contrib/footmisc/changes.txt
index 8471cde6e0..dc51273e12 100644
--- a/macros/latex/contrib/footmisc/changes.txt
+++ b/macros/latex/contrib/footmisc/changes.txt
@@ -1,3 +1,8 @@
+2022-03-08 Frank Mittelbach <Frank.Mittelbach@latex-project.org>
+
+ * footmisc.dtx (subsection{The other footnote commands}):
+ Also support optional arg of \cs{MakePerPage} (gh/03)
+
2022-03-07 Frank Mittelbach <Frank.Mittelbach@latex-project.org>
* footmisc.dtx (subsection{The other footnote commands}):
diff --git a/macros/latex/contrib/footmisc/footmisc-code.pdf b/macros/latex/contrib/footmisc/footmisc-code.pdf
index d1428e73fd..affa9e8a4d 100644
--- a/macros/latex/contrib/footmisc/footmisc-code.pdf
+++ b/macros/latex/contrib/footmisc/footmisc-code.pdf
Binary files differ
diff --git a/macros/latex/contrib/footmisc/footmisc-doc.pdf b/macros/latex/contrib/footmisc/footmisc-doc.pdf
index 71f7811af1..d738cb5e0d 100644
--- a/macros/latex/contrib/footmisc/footmisc-doc.pdf
+++ b/macros/latex/contrib/footmisc/footmisc-doc.pdf
Binary files differ
diff --git a/macros/latex/contrib/footmisc/footmisc.dtx b/macros/latex/contrib/footmisc/footmisc.dtx
index c3a31d2e6a..bafdb76df3 100644
--- a/macros/latex/contrib/footmisc/footmisc.dtx
+++ b/macros/latex/contrib/footmisc/footmisc.dtx
@@ -38,7 +38,7 @@
%<+package>\DeclareCurrentRelease{}{2022-02-14}
%<package>\ProvidesPackage{footmisc}%
%<*!debugins>
- [2022/03/07 v6.0c
+ [2022/03/08 v6.0d
%</!debugins>
%<package> a miscellany of footnote facilities%
%<*dtx>
@@ -1865,24 +1865,31 @@
% resets footnotes at each chapter start. As a result the first
% footnote on the first page of a chapter starts with 2. We
% therefore alter one \LaTeX{} internal if \pkg{perpage} is in use:
-% \changes{v6.0c}{2022/03/07}{Fix an issue in perpage packag (gh/03)}
+% \changes{v6.0c}{2022/03/07}{Fix an issue in perpage package (gh/03)}
+% \changes{v6.0d}{2022/03/07}{Also support optional arg of \cs{MakePerPage} (gh/03)}
% \begin{macrocode}
\def\@stpelt#1{\global\csname c@#1\endcsname \m@ne
\stepcounter{#1}%
- \setcounter{#1}{0}%
+ \pp@fix@MakePerPage{#1}%
+ }
+ \def\pp@fix@MakePerPage#1{%
+ \ifnum \value{#1}>\z@
+ \addtocounter{#1}\m@ne\fi
}
% \end{macrocode}
-% The above code may look a bit odd: the \cs{stepcounter} sets
-% the counter to zero and then the \cs{setcounter} does the
-% same. The reason is that \cs{stepcounter} resets other counters
-% and when perpage is loaded this results in updating counters on
-% the reset list to 1, which is precisely the problem here. So
-% the second \cs{setcounter} undoes this if it happens.
+% The above code may look a bit odd: the \cs{stepcounter} sets the
+% counter to zero and then we alter it if it is not zero. The
+% reason is that \cs{stepcounter} resets other counters and when
+% perpage is loaded this results in updating counters on the reset
+% list to 1 (or to a higher starting value if \cs{MakePerPage} is
+% used with an optional argument, which is precisely the problem
+% here. By subtracting 1 in that case we set it back to 1 lower
+% than the starting value.
%
-% But to make this fully work we also need to update a suport
+% But to make this fully work we also need to update a support
% command in \pkg{perpage}:
% \begin{macrocode}
- \def\pp@cl@end@iii\stepcounter#1\setcounter#2#3{}
+ \def\pp@cl@end@iii\stepcounter#1\pp@fix@MakePerPage#2{}
% \end{macrocode}
%
% \begin{macrocode}
diff --git a/macros/latex/contrib/robotarm/README.md b/macros/latex/contrib/robotarm/README.md
new file mode 100644
index 0000000000..f95f712205
--- /dev/null
+++ b/macros/latex/contrib/robotarm/README.md
@@ -0,0 +1,3 @@
+# `robotarm` LaTeX package
+
+This LaTeX package uses Tikz to draw parameterized 2D robot arms, for example to be used in educational material. \ No newline at end of file
diff --git a/macros/latex/contrib/robotarm/robotarm.dtx b/macros/latex/contrib/robotarm/robotarm.dtx
new file mode 100644
index 0000000000..79986b388f
--- /dev/null
+++ b/macros/latex/contrib/robotarm/robotarm.dtx
@@ -0,0 +1,940 @@
+% \iffalse meta-comment
+%
+% Copyright (C) 2021 by M.J.W. Snippe
+% -----------------------------------
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+%
+% http://www.latex-project.org/lppl.txt
+%
+% and version 1.3 or later is part of all distributions of LaTeX
+% version 2005/12/01 or later.
+%
+% This work has the LPPL maintenance status `author-maintained'.
+%
+% This work consists of the files found at github.com/max-sn/robotarm.
+%
+% \fi
+%
+% \iffalse
+%<*driver>
+\documentclass{ltxdoc}
+\input{robotarm.doc.settings.sty}
+\EnableCrossrefs
+\RecordChanges
+\begin{document}
+ \DocInput{robotarm.dtx}
+\end{document}
+%</driver>
+%
+% \fi
+%
+% \iffalse
+%
+%<*robotarm-doc-settings>
+
+\usepackage[utf8]{inputenc}
+\usepackage{xspace}
+\usepackage{multicol}
+\usepackage{geometry}
+\usepackage{mathpazo}
+\usepackage{robotarm}
+\usepackage{biblatex}
+\usepackage{hypdoc}
+\usepackage{tcolorbox}
+
+\addbibresource{robotarm.doc.bib.bib}
+\defbibheading{robotarmbibheading}[\bibname]{%
+ \clearpage\section*{#1}\addcontentsline{toc}{section}{#1}}
+
+\geometry{pass}
+
+\tcbuselibrary{documentation,breakable,minted}
+\tcbset{
+ index german settings,
+ index colorize,
+ docexample/.append style={
+ before lower={\begin{center}\begin{tikzpicture}},
+ after lower={\end{tikzpicture}\end{center}},
+ documentation minted options={autogobble},
+ breakable,
+ }
+}
+
+\makeindex
+
+\renewcommand{\MacroFont}{\color{black!70!blue}\small\ttfamily}
+
+\def\robotarmpackage{\textsl{robotarm}\xspace}
+\def\Tikz{{\normalfont Ti\textit{k}Z}\xspace}
+
+%</robotarm-doc-settings>
+%
+%<*robotarm-doc-main>
+
+\title{The \robotarmpackage package}
+\author{Marcus J.W. Snippe \\
+ \normalsize\href{mailto:maxsnippe@gmail.nl}
+ {\texttt{maxsnippe[at]gmail.nl}}}
+\date{\filedate\\[0.4em]\normalsize\fileversion}
+
+\maketitle
+
+\begin{abstract}
+ With the \robotarmpackage package, you can draw configurable planar robot arms with ease, using \Tikz. It defines the macro (\refCom{robotArm}) and a lot of ways to configure it. Additionally, you can use or redefine the macros used to draw the base link (\refCom{robotArmBaseLink}), a single link (\refCom{robotArmLink}), or the end effector (\refCom{robotArmEndEffector}).
+\end{abstract}
+
+\begin{multicols}{2}
+ \tableofcontents
+\end{multicols}
+
+\section{Macros}
+
+ \subsection{\texttt{robotArm}}
+
+ \begin{docCommand}{robotArm}{\oarg{key-value list}\marg{num}}
+ This is the main macro of the \robotarmpackage package. It is meant to be used in the \texttt{tikzpicture} environment\footnote{The first example still includes the \texttt{tikzpicture} environment, but for following examples,it will be omitted in the example code.}, from the \Tikz\cite{tantau:tikz_and_pgf} package,
+ and installs some styles for additional drawing, e.g. \refKey{/tikz/in link}.
+
+ \begin{dispExample*}{before lower={}, after lower={}}
+ \begin{tikzpicture}
+ \robotArm[config={q1=60,q2=-40,q3=-50}]{3}
+ \end{tikzpicture}
+ \end{dispExample*}
+
+ \meta{num} defines the number of links that will be drawn. For an $N$ link robot arm, the $N$\textsuperscript{th} link will be the end effector link.
+
+ The \meta{key-value list} values can consist of the keys listed below.
+
+
+
+ \begin{docKey}{geometry}{=\meta{key-value list}}{}
+
+ The \refKey{geometry} key can be used to set geometrical configuration options of the robot arm, e.g. link length and link width. The keys in \meta{key-value list} can be one or more of the following.
+
+ ^^A \begin{docKey}[][doc label=/geometry/a0]{a0}{=\meta{value}}{initially \texttt{0.0}}
+ ^^A Sets the height of the base link.
+
+ ^^A \begin{dispExample}
+ ^^A \robotArm[config={q1=60},geometry={a0=2}]{2}
+ ^^A \end{dispExample}
+ ^^A \end{docKey}
+
+ \begin{docKeys}
+ [
+ doc parameter={=\meta{value}},
+ ]
+ {
+ {
+ doc name=a,
+ doc label=/geometry/a,
+ doc description={initially \texttt{2}}
+ },
+ {
+ doc name=a\meta{num},
+ doc label=/geometry/anum,
+ doc description={initially the value of \refKey{/geometry/a}}
+ },
+ }
+
+ The \refKey{/geometry/a} key sets the default length for all links that are drawn in the \refCom{robotArm} command. For every link, this can be overruled by the dynamically created keys \refKey{/geometry/anum}, e.g. to set only the length of link 3 to 3, use \texttt{a3=3}.
+
+ \begin{dispExample}
+ \robotArm[config={q1=20,q2=-20,q3=-20},
+ geometry={a=3,a3=1}]{3}
+ \end{dispExample}
+ \end{docKeys}
+
+ \begin{docKeys}
+ [
+ doc parameter={=\meta{value}}
+ ]
+ {
+ {
+ doc name=r,
+ doc label=/geometry/r,
+ doc description={initially \texttt{0.25}}
+ },
+ {
+ doc name=r\meta{num},
+ doc label=/geometry/rnum,
+ doc description={initially the value of \refKey{/geometry/r}}
+ },
+ }
+ The \refKey{/geometry/r} key sets the default joint radius for all links drawn in the \refCom{robotArm} command. For every link this can be overruled by the dynamically created keys \refKey{/geometry/rnum}.
+
+ \begin{dispExample}
+ \robotArm[config={q1=20,q2=-20},
+ geometry={r=0.5,r1=0.3}]{2}
+ \end{dispExample}
+ \end{docKeys}
+
+ \begin{docKeys}
+ [
+ doc parameter={=\meta{value}}
+ ]
+ {
+ {
+ doc name=w,
+ doc label=/geometry/w,
+ doc description={initially \texttt{0.4}}
+ },
+ {
+ doc name=w\meta{num},
+ doc label=/geometry/wnum,
+ doc description={initially the value of \refKey{/geometry/w}}
+ },
+ }
+ The \refKey{/geometry/w} key sets the default link width for all links drawn in the \refCom{robotArm} command. For every link this can be overruled by the dynamically created keys \refKey{/geometry/wnum}.
+ \end{docKeys}
+
+ \end{docKey}
+
+ \begin{dispExample}
+ \tikzset{/robotarm/geometry={a=1}}
+ \robotArm{1}
+ \begin{scope}[xshift=3cm]
+ \robotArm{6}
+ \end{scope}
+ \end{dispExample}
+
+ \begin{docKey}[robotarm/styles]{link}{}{default \texttt{draw,fill=lightgray}}
+ \end{docKey}
+
+ \end{docCommand}
+
+ \subsection{\texttt{robotArmBaseLink}}
+
+ \begin{docCommand}{robotArmBaseLink}{\oarg{key-value list}}
+ This command is used in \refCom{robotArm} to draw the base link. It can also be used to draw your own base link. The default base link looks as follows:
+ \begin{dispExample}
+ \robotArmBaseLink
+ \end{dispExample}
+ The \meta{key-value list} can consist of the keys listed below.
+
+ \begin{docKeys}
+ [
+ doc keypath={robotarm/base link},
+ doc parameter={=\meta{value}},
+ ]
+ {
+ {
+ doc name=height,
+ doc description={initially \texttt{0.6}}
+ },
+ {
+ doc name=width,
+ doc description={initially \texttt{0.3}}
+ },
+ }
+ The width and height of the base link can be specified with these keys. The height is measured from the center of the half-circle at the top, to the base.
+ \begin{dispExample}
+ \robotArmBaseLink[width=1, height=0.6]
+ \end{dispExample}
+ \end{docKeys}
+
+ \begin{docKeys}
+ [
+ doc keypath={robotarm/base link},
+ doc parameter={=\meta{value}},
+ ]
+ {
+ {
+ doc name=world width,
+ doc description={initially \texttt{1.0}},
+ },
+ {
+ doc name=world height,
+ doc description={initially \texttt{0.3}},
+ }
+ }
+ The width and height of the `world' drawn below the base link can be specified with these keys.
+ \begin{dispExample}
+ \robotArmBaseLink[world width=5, world height=1]
+ \end{dispExample}
+ \end{docKeys}
+
+ \begin{docKeys}
+ [
+ doc keypath={robotarm/base link},
+ doc description={code executing key},
+ ]
+ {
+ {doc name=draw base link},
+ {doc name=draw world},
+ }
+ These keys are used to draw the `world' and the base link. If the configuration options above are insufficient, you can redefine these keys, as shown in the not-so-creative example below.
+ \begin{dispExample}
+ \robotArmBaseLink[
+ draw world/.code={
+ \filldraw[fill=brown] (-2,-1) rectangle (2, -0.3);
+ },
+ draw base link/.code={
+ \filldraw[fill=black!60] (-0.5, -0.3) rectangle
+ (0.5, 0.3);
+ }]
+ \end{dispExample}
+ To access the values of the keys listed above you can use the following macros in your redefinition:
+ \begin{itemize}
+ \item \docAuxCommand{RA@baselink@width},
+ \item \docAuxCommand{RA@baselink@height},
+ \item \docAuxCommand{RA@baselink@worldwidth}, and
+ \item \docAuxCommand{RA@baselink@worldheight}.
+ \end{itemize}
+ \end{docKeys}
+
+ \end{docCommand}
+
+
+ \subsection{\texttt{robotArmLink}}
+
+ \begin{docCommand}{robotArmLink}{\oarg{key-value list}}
+ This command is used in \refCom{robotArm} to draw the links in a \texttt{foreach} loop. It can also be used to draw your own link(s). The default link looks as follows:
+ \begin{dispExample}
+ \robotArmLink
+ \end{dispExample}
+ The \meta{key-value list} can consist of the keys listed below.
+
+ \begin{docKeys}
+ [
+ doc keypath={robotarm/link},
+ doc parameter={=\meta{value}},
+ ]{
+ {
+ doc name=width,
+ doc description={initially \texttt{0.4}},
+ },
+ {
+ doc name=length,
+ doc description={initially \texttt{2.0}},
+ },
+ {
+ doc name=joint radius,
+ doc description={initially \texttt{0.25}},
+ },
+ }
+ The geometrical properties of the links can be configured with these keys.
+ \begin{dispExample}
+ \robotArmLink[width=0.75,joint radius=0.4,length=5]
+ \end{dispExample}
+ \end{docKeys}
+
+ \begin{docKeys}
+ [
+ doc keypath={robotarm/link},
+ doc description={code executing key},
+ ]
+ {
+ {doc name=draw link},
+ {doc name=draw joint},
+ }
+ Same as for the base link, the actual drawing is done by calling these keys. These can also be redefined to change the drawing.
+ \begin{dispExample}
+ \robotArmLink[draw link/.code={
+ \draw[line cap=round, double=lightgray,
+ double distance=4mm]
+ (0,0) to[bend left] (2,0);}]
+ \end{dispExample}
+ Of course you can also do this globally so it applies to all links\footnote{For the end effector to change too, you have to redefine \refKey{/robotarm/end effector/draw joint} too.}.
+ \begin{dispExample}
+ \pgfkeys{/robotarm/link/draw joint/.code={
+ \path[link style] (-0.3,-0.3) rectangle ++(0.6,0.6);
+ \path[link style] (0,0) circle (0.2);}}
+ \robotArm[geometry={a=1.5},
+ config={q1=60,q2=-30,q3=-30}]{3}
+ \end{dispExample}
+ To access the values of the keys listed above you can use the following macros in your redefinition:
+ \begin{itemize}
+ \item \docAuxCommand{RA@link@width},
+ \item \docAuxCommand{RA@link@length}, and
+ \item \docAuxCommand{RA@link@jointradius}.
+ \end{itemize}
+ \end{docKeys}
+
+ \end{docCommand}
+
+ \subsection{\texttt{robotArmEndEffector}}
+
+ \begin{docCommand}{robotArmEndEffector}{\oarg{key-value list}}
+ This command is used in \refCom{robotArm} to draw the final link with the end effector attached. You can also use it outside that command to draw your own end effector.
+ \begin{dispExample}
+ \robotArmEndEffector
+ \end{dispExample}
+ The \meta{key-value list} can consist of the keys listed below.
+
+ \begin{docKeys}
+ [
+ doc keypath={robotarm/end effector},
+ doc parameter={=\meta{value}},
+ ]
+ {
+ {
+ doc name=width,
+ doc description={initially \texttt{0.4}},
+ },
+ {
+ doc name=length,
+ doc description={initially \texttt{2.0}},
+ },
+ {
+ doc name=joint radius,
+ doc description={initially \texttt{0.25}},
+ },
+ {
+ doc name=gripper radius,
+ doc description={initially \texttt{0.3}},
+ },
+ {
+ doc name=gripper opening angle,
+ doc description={initially \texttt{60}},
+ doc parameter={=\meta{angle}},
+ },
+ }
+ The geometrical properties of the final link and the end effector, which defaults to a gripper, can be tuned with these keys. Due to some trigonometric functions in the drawing code of \refKey{/robotarm/end effector/draw end effector}, \refKey{/robotarm/end effector/gripper radius} should not be smaller than half of \refKey{/robotarm/end effector/width}.
+
+ \end{docKeys}
+
+ \begin{docKeys}
+ [
+ doc keypath={robotarm/end effector},
+ doc description={code executing key},
+ ]
+ {
+ {doc name=draw link},
+ {doc name=draw joint},
+ {doc name=draw end effector},
+ }
+ Again the actual drawing is done by calling these keys. And these can also be redefined to change how the end effector will look, as shown in the example below.
+ \begin{dispExample}
+ \robotArm[
+ config={q1=100,q2=-100},
+ end effector/.cd,
+ draw link/.code={
+ \draw[line cap=round, double=lightgray,
+ double distance=4mm]
+ (0,0) to[bend left] (2, 0.8);},
+ draw end effector/.code={
+ \path[link style]
+ (1.8, 0.8) arc (180:0:0.2) -- (2.1, 0.4)
+ -- (1.9, 0.4) -- cycle;
+ \path[link style]
+ (2.05,0.4) rectangle (1.95,0.0);
+ \fill (2,0) circle (0.05);}]{2}
+ \end{dispExample}
+ To access the values of the keys listed above you can use the following macros in your redefinition:
+ \begin{itemize}
+ \item \docAuxCommand{RA@endeff@width},
+ \item \docAuxCommand{RA@endeff@length},
+ \item \docAuxCommand{RA@endeff@jointradius},
+ \item \docAuxCommand{RA@endeff@gripperradius}, and
+ \item \docAuxCommand{RA@endeff@gripperopeningangle}.
+ \end{itemize}
+ \end{docKeys}
+
+ \end{docCommand}
+
+\section{\Tikz styles}
+
+ \begin{docKeys}
+ [
+ doc keypath=tikz,
+ doc description=style,
+ ]
+ {
+ {
+ doc name=in link,
+ doc parameter={=\meta{number}},
+ },
+ {
+ doc name=in base link,
+ },
+ {
+ doc name=in world,
+ },
+ {
+ doc name=in end effector,
+ }
+ }
+ Only installed after calling \refCom{robotArm} at least once (last called macro defines these styles, but within scope can be made unique).
+ \end{docKeys}
+
+ \begin{docKey}[tikz]{link style}{}{default \texttt{/robotarm/styles/link}}
+ Always installed, forwards to \refKey{/robotarm/styles/link}. Only works one way. To change link styles used in \refCom{robotArm}, change the appropriate \refKey{/robotarm/styles/link}.
+ \begin{dispExample}
+ \tikzset{link style/.style={draw=none, fill=red!50}}
+ \robotArm[config={q1=40},styles={
+ link 1/.style={fill=blue!50}}]{3}
+ \end{dispExample}
+ \end{docKey}
+
+
+ %^^A \section{\robotarmpackage keys}
+ %^^A \begin{docKeys}
+ %^^A [
+ %^^A doc keypath=robotarm/annotations/labels,
+ %^^A ]{
+ %^^A {
+ %^^A doc name=a,
+ %^^A doc description={no default, initially \texttt{a}},
+ %^^A },
+ %^^A {
+ %^^A doc name=q,
+ %^^A doc description={no default, initially \texttt{q}},
+ %^^A }
+ %^^A }
+ %^^A \begin{dispExample}
+ %^^A \robotArm[
+ %^^A config={q1=60,q2=-40,q3=40},
+ %^^A annotations/labels/a=l,
+ %^^A annotations/labels/q=\theta,
+ %^^A annotations/labels/q3=?]{3}
+ %^^A \end{dispExample}
+ %^^A \end{docKeys}
+
+ %^^A \section{\Tikz keys}
+
+
+ %^^A \begin{docKeys}
+ %^^A [
+ %^^A doc keypath=robotarm/frames,
+ %^^A doc description={style},
+ %^^A ]{
+ %^^A {doc name=in link 0},
+ %^^A {doc name=in link 1},
+ %^^A {doc name=in link 2},
+ %^^A {doc name=in link 3},
+ %^^A {doc name=in link 4},
+ %^^A {doc name=in link 5},
+ %^^A {doc name=in link 6}
+ %^^A }
+ %^^A \end{docKeys}
+
+
+%</robotarm-doc-main>
+%
+%<*robotarm-doc-bib>
+@manual{tantau:tikz_and_pgf,
+ author = {Till Tantau},
+ title = {The TikZ and PGF Packages},
+ subtitle = {Manual for version 3.1.8b},
+ url = {https://mirrors.ctan.org/graphics/pgf/base/doc/pgfmanual.pdf},
+ date = {2021-03-19},
+}
+%</robotarm-doc-bib>
+%
+% \fi
+%
+% \CheckSum{286}
+%
+% \CharacterTable
+% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
+% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
+% Digits \0\1\2\3\4\5\6\7\8\9
+% Exclamation \! Double quote \" Hash (number) \#
+% Dollar \$ Percent \% Ampersand \&
+% Acute accent \' Left paren \( Right paren \)
+% Asterisk \* Plus \+ Comma \,
+% Minus \- Point \. Solidus \/
+% Colon \: Semicolon \; Less than \<
+% Equals \= Greater than \> Question mark \?
+% Commercial at \@ Left bracket \[ Backslash \\
+% Right bracket \] Circumflex \^ Underscore \_
+% Grave accent \` Left brace \{ Vertical bar \|
+% Right brace \} Tilde \~}
+%
+%
+% \DoNotIndex{\angleannotationcase,\link@angleannotspacing,\begin,\begingroup,\DeclareDocumentCommand,\draw,\end,\endgroup,\fi,\foreach,\ifcase,\ifnum,\ifRA@robotarm@drawannotations,\lenghtannotationspacing,\link@lenght,\link@radius,\link@startangle,\link@width,\lengthannotationspacing,\link@length,\link@case}
+%
+% \changes{v0.1}{2022/03/08}{Initial version for publishing}
+%
+% \GetFileInfo{robotarm.sty}
+%
+% \input{robotarm.doc.main.tex}
+%
+% \StopEventually{\printbibliography[heading=robotarmbibheading]\printindex\PrintChanges}
+%
+% \section{Implementation}
+% \begin{macrocode}
+%<*robotarm-package>
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{robotarm}
+ [2022/03/08 v0.1 Tikz commands to draw planar robot arms]
+
+\RequirePackage{tikz}
+
+\usetikzlibrary{patterns}
+
+\makeatletter
+
+\newif\ifRA@robotarm@drawannotations
+
+\pgfkeys{
+ /robotarm/base link/.cd,
+ width/.code={\pgfmathsetmacro\RA@baselink@width{#1}},
+ width=0.6,
+ height/.code={\pgfmathsetmacro\RA@baselink@height{#1}},
+ height=0.3,
+ world width/.code={\pgfmathsetmacro\RA@baselink@worldwidth{#1}},
+ world width=1.0,
+ world height/.code={\pgfmathsetmacro\RA@baselink@worldheight{#1}},
+ world height=0.3,
+ draw base link/.code={%
+ \path[link style]
+ (-1/2*\RA@baselink@width,0)
+ arc (180:0:1/2*\RA@baselink@width)
+ -- ++ (0, -\RA@baselink@height)
+ -- ++ (-\RA@baselink@width, 0)
+ -- cycle;
+ },
+ draw world/.code={%
+ \path[world style]
+ (-1/2*\RA@baselink@worldwidth,-\RA@baselink@height)
+ arc (180:360:{1/2*\RA@baselink@worldwidth}
+ and {\RA@baselink@worldheight}) -- cycle;
+ \path[draw, world style]
+ (-1/2*\RA@baselink@worldwidth,-\RA@baselink@height)
+ -- ++(\RA@baselink@worldwidth,0);
+ },
+ /robotarm/link/.cd,
+ width/.code={\pgfmathsetmacro\RA@link@width{#1}},
+ width=0.4,
+ length/.code={\pgfmathsetmacro\RA@link@length{#1}},
+ length=2.0,
+ joint radius/.code={\pgfmathsetmacro\RA@link@jointradius{#1}},
+ joint radius=0.25,
+ draw link/.code={%
+ \path[link style]
+ (0,1/2*\RA@link@width)
+ -- ++ ( \RA@link@length, 0)
+ arc (90:-90:1/2*\RA@link@width)
+ -- ++ (-\RA@link@length, 0)
+ arc (270:90:1/2*\RA@link@width)
+ -- cycle;
+ },
+ draw joint/.code={%
+ \path[link style]
+ (0,0) circle (\RA@link@jointradius);
+ \path[link style]
+ (0,0) circle (1/3*\RA@link@jointradius);
+ },
+ /robotarm/end effector/.cd,
+ width/.code={\pgfmathsetmacro\RA@endeff@width{#1}},
+ width=0.4,
+ length/.code={\pgfmathsetmacro\RA@endeff@length{#1}},
+ length=2,
+ joint radius/.code={\pgfmathsetmacro\RA@endeff@jointradius{#1}},
+ joint radius=0.25,
+ gripper radius/.code={\pgfmathsetmacro\RA@endeff@gripperradius{#1}},
+ gripper radius=0.3,
+ gripper opening angle/.code={%
+ \pgfmathsetmacro\RA@endeff@gripperopeningangle{#1}},
+ gripper opening angle=60,
+ draw joint/.code={%
+ \path[link style]
+ (0,0) circle (\RA@endeff@jointradius);
+ \path[link style]
+ (0,0) circle (1/3*\RA@endeff@jointradius);
+ },
+ draw link/.code={%
+ \pgfmathsetmacro{\link@startangle}{%
+ 180-asin(1/2*\RA@endeff@width/\RA@endeff@gripperradius)}
+ \pgfmathsetmacro{\link@endangle}{%
+ 180+asin(1/2*\RA@endeff@width/\RA@endeff@gripperradius)}
+
+ \path[link style]
+ (\RA@endeff@length, 0)
+ ++ (\link@startangle:\RA@endeff@gripperradius)
+ arc (\link@startangle:\link@endangle:\RA@endeff@gripperradius)
+ -- (0,0|-0,-1/2*\RA@endeff@width)
+ arc (-90:90:1/2*\RA@endeff@width)
+ -- cycle;
+ },
+ draw end effector/.code={
+ \draw[link style]
+ (\RA@endeff@length, 0)
+ ++ (-1/2*\RA@endeff@gripperopeningangle:%
+ \RA@endeff@gripperradius)
+ arc [start angle=-1/2*\RA@endeff@gripperopeningangle,
+ delta angle=-360+\RA@endeff@gripperopeningangle,
+ radius=\RA@endeff@gripperradius]
+ -- ++(180+1/2*\RA@endeff@gripperopeningangle:%
+ 0.4*\RA@endeff@gripperradius)
+ arc [start angle=1/2*\RA@endeff@gripperopeningangle,
+ delta angle=360-\RA@endeff@gripperopeningangle,
+ radius=0.6*\RA@endeff@gripperradius]
+ -- ++(-1/2*\RA@endeff@gripperopeningangle:%
+ 0.4*\RA@endeff@gripperradius)
+ -- cycle;
+ },
+ /robotarm/.cd,
+ draw annotations/.is if=RA@robotarm@drawannotations,
+ draw annotations=true,
+ every annotation/.style={},
+ every length annotation/.style={},
+ every length annotation arrow/.style={draw,->},
+ every length annotation node/.style={circle,inner sep=0.5pt},
+ every length annotation help line/.style={draw,help lines},
+ every angle annotation/.style={},
+ every angle annotation arrow/.style={draw,->},
+ every angle annotation node/.style={},
+ every angle annotation help line/.style={draw,help lines},
+ base link/.code=\pgfkeys{/robotarm/base link/.cd,#1},
+ link/.code=\pgfkeys{/robotarm/link/.cd,#1},
+ end effector/.code=\pgfkeys{/robotarm/end effector/.cd,#1},
+ geometry/.code=\pgfkeys{/robotarm/geometry/.cd,#1},
+ config/.code=\pgfkeys{/robotarm/config/.cd,#1},
+ spacing/.code=\pgfkeys{/robotarm/annotations/spacing/.cd,#1},
+ labels/.code=\pgfkeys{/robotarm/annotations/labels/.cd,#1},
+ styles/.code=\pgfkeys{/robotarm/styles/.cd,#1},
+ /robotarm/geometry/.cd,
+ a0/.initial=0,
+ a/.initial=2,
+ r/.initial=0.25,
+ w/.initial=0.4,
+ /robotarm/config/q/.initial=0,
+ /robotarm/frames/.cd,
+ in link 0/.style={},
+ in end effector/.style={
+ /robotarm/frames/in link \RA@robotarm@numlinks,
+ shift={%
+ (\pgfkeysvalueof{/robotarm/geometry/a\RA@robotarm@numlinks},0)},
+ },
+ in world/.style={
+ shift={(0,-\RA@baselink@height)}
+ },
+ /robotarm/styles/.cd,
+ world/.style={pattern=north west lines},
+ link/.style={
+ draw,
+ fill=lightgray,
+ },
+ link 0/.style={/robotarm/styles/link},
+ /robotarm/annotations/.cd,
+ spacing/.cd,
+ a/.initial=3,
+ q/.initial=1/2,
+ /robotarm/annotations/.cd,
+ labels/.cd,
+ a/.initial=a,
+ q/.initial=q,
+}
+\tikzset{
+ link style/.style={/robotarm/styles/link},
+ world style/.style={/robotarm/styles/world},
+}
+% \end{macrocode}
+% \begin{macro}{\robotarmset}
+% \begin{macrocode}
+\newcommand\robotarmset[1]{%
+ \pgfkeys{/robotarm/.cd,#1}%
+}
+% \end{macrocode}
+% \end{macro}
+% \begin{macro}{\robotArmLink}
+% \begin{macrocode}
+\newcommand\robotArmLink[1][]{
+ \begingroup
+ \pgfkeys{/robotarm/link/.cd,#1}
+
+ \pgfkeys{/robotarm/link/draw link}
+ \pgfkeys{/robotarm/link/draw joint}
+ \endgroup
+}
+% \end{macrocode}
+% \end{macro}
+% \begin{macro}{\robotArmEndEffector}
+% \begin{macrocode}
+\newcommand\robotArmEndEffector[1][]{
+ \begingroup
+ \pgfkeys{/robotarm/end effector/.cd,#1}
+
+ \pgfkeys{/robotarm/end effector/draw link}
+ \pgfkeys{/robotarm/end effector/draw joint}
+ \pgfkeys{/robotarm/end effector/draw end effector}
+
+ \endgroup
+}
+% \end{macrocode}
+% \end{macro}
+% \begin{macro}{\robotArmBaseLink}
+% \begin{macrocode}
+\newcommand\robotArmBaseLink[1][]{
+ \begingroup
+ \pgfkeys{/robotarm/base link/.cd,#1}
+
+ \pgfkeys{/robotarm/base link/draw world}
+ \pgfkeys{/robotarm/base link/draw base link}
+ \endgroup
+}
+% \end{macrocode}
+% \end{macro}
+% \begin{macro}{\robotArm}
+% \begin{macrocode}
+\newcommand\robotArm[2][]{
+ \pgfmathtruncatemacro\RA@robotarm@numlinks{#2}
+ \def\@tmpkeys{}
+ \foreach \@link [remember=\@link as \@prevlink (initially 0)] in %
+ {1,...,\RA@robotarm@numlinks}{
+ \xdef\@tmpkeys{\@tmpkeys%
+ /robotarm/geometry/a\@link/.initial=%
+ \pgfkeysvalueof{/robotarm/geometry/a},%
+ /robotarm/geometry/r\@link/.initial=%
+ \pgfkeysvalueof{/robotarm/geometry/r},%
+ /robotarm/geometry/w\@link/.initial=%
+ \pgfkeysvalueof{/robotarm/geometry/w},%
+ /robotarm/config/q\@link/.initial=%
+ \pgfkeysvalueof{/robotarm/config/q},%
+ /robotarm/styles/link \@link/.style={/robotarm/styles/link},%
+ /robotarm/annotations/labels/a\@link/.initial={%
+ $\pgfkeysvalueof{/robotarm/annotations/labels/a}_{\@link}$},%
+ /robotarm/annotations/labels/q\@link/.initial={%
+ $\pgfkeysvalueof{/robotarm/annotations/labels/q}_{\@link}$},%
+ /robotarm/annotations/spacing/a\@link/.initial={%
+ \pgfkeysvalueof{/robotarm/annotations/spacing/a}},%
+ /robotarm/annotations/spacing/q\@link/.initial={%
+ \pgfkeysvalueof{/robotarm/annotations/spacing/q}},%
+ }
+ }
+ \expandafter\pgfkeys\expandafter{\@tmpkeys}
+ \pgfkeys{/robotarm/.cd,#1}
+ \def\@tmpkeys{}
+ \foreach \@link [remember=\@link as \@prevlink (initially 0)] in %
+ {1,...,\RA@robotarm@numlinks}{
+ \xdef\@tmpkeys{\@tmpkeys%
+ /robotarm/frames/in link \@link/.style={%
+ /robotarm/frames/in link \@prevlink,
+ /tikz/shift={%
+ (\pgfkeysvalueof{/robotarm/geometry/a\@prevlink},0)},
+ /tikz/rotate={\pgfkeysvalueof{/robotarm/config/q\@link}},
+ },
+ }
+ }
+ \expandafter\pgfkeys\expandafter{\@tmpkeys}
+
+ \begin{scope}[/robotarm/frames/in link 0,
+ link style/.style={/robotarm/styles/link 0}]
+ \robotArmBaseLink
+ \end{scope}
+
+ \foreach\link@num in {1,...,\RA@robotarm@numlinks}{
+ \begin{scope}[/robotarm/frames/in link \link@num,
+ link style/.style={/robotarm/styles/link \link@num}]
+
+ \ifnum\link@num<\RA@robotarm@numlinks
+ \robotArmLink[
+ joint radius=\pgfkeysvalueof{/robotarm/geometry/r\link@num},
+ length=\pgfkeysvalueof{/robotarm/geometry/a\link@num},
+ width=\pgfkeysvalueof{/robotarm/geometry/w\link@num},
+ ]
+ \else
+ \robotArmEndEffector[
+ joint radius=\pgfkeysvalueof{/robotarm/geometry/r\link@num},
+ length=\pgfkeysvalueof{/robotarm/geometry/a\link@num},
+ width=\pgfkeysvalueof{/robotarm/geometry/w\link@num},
+ ]
+ \fi
+ \end{scope}
+ }
+ \foreach\link@num in {1,...,\RA@robotarm@numlinks}{
+ \begin{scope}[/robotarm/frames/in link \link@num]
+ \pgfmathsetmacro\link@length{\pgfkeysvalueof{%
+ /robotarm/geometry/a\link@num}}
+ \pgfmathsetmacro\link@angle{\pgfkeysvalueof{%
+ /robotarm/config/q\link@num}}
+
+ \ifRA@robotarm@drawannotations
+ \pgfmathsetmacro\link@lengthannotspacing{%
+ \pgfkeysvalueof{/robotarm/annotations/spacing/a\link@num}*
+ \pgfkeysvalueof{/robotarm/geometry/r\link@num}}
+ \pgfmathsetmacro\link@angleannotspacing{%
+ \pgfkeysvalueof{/robotarm/annotations/spacing/q\link@num}*
+ \link@length}
+
+ % Length annotation help lines
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation help line]
+ (0,0) -- (\link@length,0);
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation help line]
+ (0,0) -- ++ (0,{\link@lengthannotspacing +
+ 0.1*sign(\link@lengthannotspacing)});
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation help line]
+ (\link@length,0) -- ++ (0,{\link@lengthannotspacing +
+ 0.1*sign(\link@lengthannotspacing)});
+% \end{macrocode}
+% Length annotation node
+% \begin{macrocode}
+ \path (0,\link@lengthannotspacing)
+ -- coordinate[pos=0.5] (coor) ++ (\link@length,0);
+ \node[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation node]
+ at (coor) (tag)
+ {\pgfkeysvalueof{/robotarm/annotations/labels/a\link@num}};
+% \end{macrocode}
+% Length annotation arrows
+% \begin{macrocode}
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation arrow]
+ (tag) -- (0,\link@lengthannotspacing);
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation arrow]
+ (tag) -- (\link@length,\link@lengthannotspacing);
+
+ \pgfmathsetmacro\angleannotationcase{%
+ ifthenelse(\link@angle==0.0, 0, 1)}
+ \ifnum\angleannotationcase>0
+ % Angle annotation help lines
+ \path[/robotarm/every annotation,
+ /robotarm/every angle annotation,
+ /robotarm/every angle annotation help line]
+ (0,0) -- ++(-\link@angle:{\link@angleannotspacing+0.1});
+ \path[/robotarm/every annotation,
+ /robotarm/every angle annotation,
+ /robotarm/every angle annotation help line]
+ (0,0) -- ++(0:{\link@angleannotspacing+0.1});
+
+ % Angle annotation arrow
+ \path[/robotarm/every annotation,
+ /robotarm/every angle annotation,
+ /robotarm/every angle annotation arrow]
+ (0,0) ++ (-\link@angle:\link@angleannotspacing)
+ arc (-\link@angle:0:\link@angleannotspacing);
+
+ % Angle annotation node
+ \node[/robotarm/every annotation,
+ /robotarm/every angle annotation,
+ /robotarm/every angle annotation node]
+ at (-\link@angle/2:\link@angleannotspacing+0.3)
+ {\pgfkeysvalueof{/robotarm/annotations/labels/q\link@num}};
+ \fi
+ \fi
+ \end{scope}
+ }
+% \end{macrocode}
+% Install \Tikz styles for coordinate transformations.
+% \begin{macrocode}
+ \tikzset{
+ in link/.style={/robotarm/frames/in link #1},
+ in base link/.style={/robotarm/frames/in link 0},
+ in end effector/.style={/robotarm/frames/in end effector},
+ in world/.style={/robotarm/frames/in world},
+ }
+}
+% \end{macrocode}
+% \end{macro}
+% \begin{macrocode}
+\makeatother
+%</robotarm-package>
+% \end{macrocode}
+% \Finale
+\endinput \ No newline at end of file
diff --git a/macros/latex/contrib/robotarm/robotarm.pdf b/macros/latex/contrib/robotarm/robotarm.pdf
new file mode 100644
index 0000000000..ab9689f9d9
--- /dev/null
+++ b/macros/latex/contrib/robotarm/robotarm.pdf
Binary files differ
diff --git a/macros/latex/contrib/robotarm/robotarm.sty b/macros/latex/contrib/robotarm/robotarm.sty
new file mode 100644
index 0000000000..5fb7f730aa
--- /dev/null
+++ b/macros/latex/contrib/robotarm/robotarm.sty
@@ -0,0 +1,375 @@
+%%
+%% This is file `robotarm.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% robotarm.dtx (with options: `robotarm-package')
+%%
+%% This is a generated file.
+%%
+%% Copyright (C) 2021 by M.J.W. Snippe
+%% -----------------------------------
+%%
+%% This file may be distributed and/or modified under the
+%% conditions of the LaTeX Project Public License, either
+%% version 1.3 of this license or (at your option) any later
+%% version. The latest version of this license is in:
+%%
+%% http://www.latex-project.org/lppl.txt
+%%
+%% and version 1.3 or later is part of all distributions of
+%% LaTeX version 2005/12/01 or later.
+%%
+%% This work has the LPPL maintenance status
+%% `author-maintained'.
+%%
+%% This work consists of the files found at github.com/max-sn/robotarm.
+%%
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{robotarm}
+ [2022/03/08 v0.1 Tikz commands to draw planar robot arms]
+
+\RequirePackage{tikz}
+
+\usetikzlibrary{patterns}
+
+\makeatletter
+
+\newif\ifRA@robotarm@drawannotations
+
+\pgfkeys{
+ /robotarm/base link/.cd,
+ width/.code={\pgfmathsetmacro\RA@baselink@width{#1}},
+ width=0.6,
+ height/.code={\pgfmathsetmacro\RA@baselink@height{#1}},
+ height=0.3,
+ world width/.code={\pgfmathsetmacro\RA@baselink@worldwidth{#1}},
+ world width=1.0,
+ world height/.code={\pgfmathsetmacro\RA@baselink@worldheight{#1}},
+ world height=0.3,
+ draw base link/.code={%
+ \path[link style]
+ (-1/2*\RA@baselink@width,0)
+ arc (180:0:1/2*\RA@baselink@width)
+ -- ++ (0, -\RA@baselink@height)
+ -- ++ (-\RA@baselink@width, 0)
+ -- cycle;
+ },
+ draw world/.code={%
+ \path[world style]
+ (-1/2*\RA@baselink@worldwidth,-\RA@baselink@height)
+ arc (180:360:{1/2*\RA@baselink@worldwidth}
+ and {\RA@baselink@worldheight}) -- cycle;
+ \path[draw, world style]
+ (-1/2*\RA@baselink@worldwidth,-\RA@baselink@height)
+ -- ++(\RA@baselink@worldwidth,0);
+ },
+ /robotarm/link/.cd,
+ width/.code={\pgfmathsetmacro\RA@link@width{#1}},
+ width=0.4,
+ length/.code={\pgfmathsetmacro\RA@link@length{#1}},
+ length=2.0,
+ joint radius/.code={\pgfmathsetmacro\RA@link@jointradius{#1}},
+ joint radius=0.25,
+ draw link/.code={%
+ \path[link style]
+ (0,1/2*\RA@link@width)
+ -- ++ ( \RA@link@length, 0)
+ arc (90:-90:1/2*\RA@link@width)
+ -- ++ (-\RA@link@length, 0)
+ arc (270:90:1/2*\RA@link@width)
+ -- cycle;
+ },
+ draw joint/.code={%
+ \path[link style]
+ (0,0) circle (\RA@link@jointradius);
+ \path[link style]
+ (0,0) circle (1/3*\RA@link@jointradius);
+ },
+ /robotarm/end effector/.cd,
+ width/.code={\pgfmathsetmacro\RA@endeff@width{#1}},
+ width=0.4,
+ length/.code={\pgfmathsetmacro\RA@endeff@length{#1}},
+ length=2,
+ joint radius/.code={\pgfmathsetmacro\RA@endeff@jointradius{#1}},
+ joint radius=0.25,
+ gripper radius/.code={\pgfmathsetmacro\RA@endeff@gripperradius{#1}},
+ gripper radius=0.3,
+ gripper opening angle/.code={%
+ \pgfmathsetmacro\RA@endeff@gripperopeningangle{#1}},
+ gripper opening angle=60,
+ draw joint/.code={%
+ \path[link style]
+ (0,0) circle (\RA@endeff@jointradius);
+ \path[link style]
+ (0,0) circle (1/3*\RA@endeff@jointradius);
+ },
+ draw link/.code={%
+ \pgfmathsetmacro{\link@startangle}{%
+ 180-asin(1/2*\RA@endeff@width/\RA@endeff@gripperradius)}
+ \pgfmathsetmacro{\link@endangle}{%
+ 180+asin(1/2*\RA@endeff@width/\RA@endeff@gripperradius)}
+
+ \path[link style]
+ (\RA@endeff@length, 0)
+ ++ (\link@startangle:\RA@endeff@gripperradius)
+ arc (\link@startangle:\link@endangle:\RA@endeff@gripperradius)
+ -- (0,0|-0,-1/2*\RA@endeff@width)
+ arc (-90:90:1/2*\RA@endeff@width)
+ -- cycle;
+ },
+ draw end effector/.code={
+ \draw[link style]
+ (\RA@endeff@length, 0)
+ ++ (-1/2*\RA@endeff@gripperopeningangle:%
+ \RA@endeff@gripperradius)
+ arc [start angle=-1/2*\RA@endeff@gripperopeningangle,
+ delta angle=-360+\RA@endeff@gripperopeningangle,
+ radius=\RA@endeff@gripperradius]
+ -- ++(180+1/2*\RA@endeff@gripperopeningangle:%
+ 0.4*\RA@endeff@gripperradius)
+ arc [start angle=1/2*\RA@endeff@gripperopeningangle,
+ delta angle=360-\RA@endeff@gripperopeningangle,
+ radius=0.6*\RA@endeff@gripperradius]
+ -- ++(-1/2*\RA@endeff@gripperopeningangle:%
+ 0.4*\RA@endeff@gripperradius)
+ -- cycle;
+ },
+ /robotarm/.cd,
+ draw annotations/.is if=RA@robotarm@drawannotations,
+ draw annotations=true,
+ every annotation/.style={},
+ every length annotation/.style={},
+ every length annotation arrow/.style={draw,->},
+ every length annotation node/.style={circle,inner sep=0.5pt},
+ every length annotation help line/.style={draw,help lines},
+ every angle annotation/.style={},
+ every angle annotation arrow/.style={draw,->},
+ every angle annotation node/.style={},
+ every angle annotation help line/.style={draw,help lines},
+ base link/.code=\pgfkeys{/robotarm/base link/.cd,#1},
+ link/.code=\pgfkeys{/robotarm/link/.cd,#1},
+ end effector/.code=\pgfkeys{/robotarm/end effector/.cd,#1},
+ geometry/.code=\pgfkeys{/robotarm/geometry/.cd,#1},
+ config/.code=\pgfkeys{/robotarm/config/.cd,#1},
+ spacing/.code=\pgfkeys{/robotarm/annotations/spacing/.cd,#1},
+ labels/.code=\pgfkeys{/robotarm/annotations/labels/.cd,#1},
+ styles/.code=\pgfkeys{/robotarm/styles/.cd,#1},
+ /robotarm/geometry/.cd,
+ a0/.initial=0,
+ a/.initial=2,
+ r/.initial=0.25,
+ w/.initial=0.4,
+ /robotarm/config/q/.initial=0,
+ /robotarm/frames/.cd,
+ in link 0/.style={},
+ in end effector/.style={
+ /robotarm/frames/in link \RA@robotarm@numlinks,
+ shift={%
+ (\pgfkeysvalueof{/robotarm/geometry/a\RA@robotarm@numlinks},0)},
+ },
+ in world/.style={
+ shift={(0,-\RA@baselink@height)}
+ },
+ /robotarm/styles/.cd,
+ world/.style={pattern=north west lines},
+ link/.style={
+ draw,
+ fill=lightgray,
+ },
+ link 0/.style={/robotarm/styles/link},
+ /robotarm/annotations/.cd,
+ spacing/.cd,
+ a/.initial=3,
+ q/.initial=1/2,
+ /robotarm/annotations/.cd,
+ labels/.cd,
+ a/.initial=a,
+ q/.initial=q,
+}
+\tikzset{
+ link style/.style={/robotarm/styles/link},
+ world style/.style={/robotarm/styles/world},
+}
+\newcommand\robotarmset[1]{%
+ \pgfkeys{/robotarm/.cd,#1}%
+}
+\newcommand\robotArmLink[1][]{
+ \begingroup
+ \pgfkeys{/robotarm/link/.cd,#1}
+
+ \pgfkeys{/robotarm/link/draw link}
+ \pgfkeys{/robotarm/link/draw joint}
+ \endgroup
+}
+\newcommand\robotArmEndEffector[1][]{
+ \begingroup
+ \pgfkeys{/robotarm/end effector/.cd,#1}
+
+ \pgfkeys{/robotarm/end effector/draw link}
+ \pgfkeys{/robotarm/end effector/draw joint}
+ \pgfkeys{/robotarm/end effector/draw end effector}
+
+ \endgroup
+}
+\newcommand\robotArmBaseLink[1][]{
+ \begingroup
+ \pgfkeys{/robotarm/base link/.cd,#1}
+
+ \pgfkeys{/robotarm/base link/draw world}
+ \pgfkeys{/robotarm/base link/draw base link}
+ \endgroup
+}
+\newcommand\robotArm[2][]{
+ \pgfmathtruncatemacro\RA@robotarm@numlinks{#2}
+ \def\@tmpkeys{}
+ \foreach \@link [remember=\@link as \@prevlink (initially 0)] in %
+ {1,...,\RA@robotarm@numlinks}{
+ \xdef\@tmpkeys{\@tmpkeys%
+ /robotarm/geometry/a\@link/.initial=%
+ \pgfkeysvalueof{/robotarm/geometry/a},%
+ /robotarm/geometry/r\@link/.initial=%
+ \pgfkeysvalueof{/robotarm/geometry/r},%
+ /robotarm/geometry/w\@link/.initial=%
+ \pgfkeysvalueof{/robotarm/geometry/w},%
+ /robotarm/config/q\@link/.initial=%
+ \pgfkeysvalueof{/robotarm/config/q},%
+ /robotarm/styles/link \@link/.style={/robotarm/styles/link},%
+ /robotarm/annotations/labels/a\@link/.initial={%
+ $\pgfkeysvalueof{/robotarm/annotations/labels/a}_{\@link}$},%
+ /robotarm/annotations/labels/q\@link/.initial={%
+ $\pgfkeysvalueof{/robotarm/annotations/labels/q}_{\@link}$},%
+ /robotarm/annotations/spacing/a\@link/.initial={%
+ \pgfkeysvalueof{/robotarm/annotations/spacing/a}},%
+ /robotarm/annotations/spacing/q\@link/.initial={%
+ \pgfkeysvalueof{/robotarm/annotations/spacing/q}},%
+ }
+ }
+ \expandafter\pgfkeys\expandafter{\@tmpkeys}
+ \pgfkeys{/robotarm/.cd,#1}
+ \def\@tmpkeys{}
+ \foreach \@link [remember=\@link as \@prevlink (initially 0)] in %
+ {1,...,\RA@robotarm@numlinks}{
+ \xdef\@tmpkeys{\@tmpkeys%
+ /robotarm/frames/in link \@link/.style={%
+ /robotarm/frames/in link \@prevlink,
+ /tikz/shift={%
+ (\pgfkeysvalueof{/robotarm/geometry/a\@prevlink},0)},
+ /tikz/rotate={\pgfkeysvalueof{/robotarm/config/q\@link}},
+ },
+ }
+ }
+ \expandafter\pgfkeys\expandafter{\@tmpkeys}
+
+ \begin{scope}[/robotarm/frames/in link 0,
+ link style/.style={/robotarm/styles/link 0}]
+ \robotArmBaseLink
+ \end{scope}
+
+ \foreach\link@num in {1,...,\RA@robotarm@numlinks}{
+ \begin{scope}[/robotarm/frames/in link \link@num,
+ link style/.style={/robotarm/styles/link \link@num}]
+
+ \ifnum\link@num<\RA@robotarm@numlinks
+ \robotArmLink[
+ joint radius=\pgfkeysvalueof{/robotarm/geometry/r\link@num},
+ length=\pgfkeysvalueof{/robotarm/geometry/a\link@num},
+ width=\pgfkeysvalueof{/robotarm/geometry/w\link@num},
+ ]
+ \else
+ \robotArmEndEffector[
+ joint radius=\pgfkeysvalueof{/robotarm/geometry/r\link@num},
+ length=\pgfkeysvalueof{/robotarm/geometry/a\link@num},
+ width=\pgfkeysvalueof{/robotarm/geometry/w\link@num},
+ ]
+ \fi
+ \end{scope}
+ }
+ \foreach\link@num in {1,...,\RA@robotarm@numlinks}{
+ \begin{scope}[/robotarm/frames/in link \link@num]
+ \pgfmathsetmacro\link@length{\pgfkeysvalueof{%
+ /robotarm/geometry/a\link@num}}
+ \pgfmathsetmacro\link@angle{\pgfkeysvalueof{%
+ /robotarm/config/q\link@num}}
+
+ \ifRA@robotarm@drawannotations
+ \pgfmathsetmacro\link@lengthannotspacing{%
+ \pgfkeysvalueof{/robotarm/annotations/spacing/a\link@num}*
+ \pgfkeysvalueof{/robotarm/geometry/r\link@num}}
+ \pgfmathsetmacro\link@angleannotspacing{%
+ \pgfkeysvalueof{/robotarm/annotations/spacing/q\link@num}*
+ \link@length}
+
+ % Length annotation help lines
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation help line]
+ (0,0) -- (\link@length,0);
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation help line]
+ (0,0) -- ++ (0,{\link@lengthannotspacing +
+ 0.1*sign(\link@lengthannotspacing)});
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation help line]
+ (\link@length,0) -- ++ (0,{\link@lengthannotspacing +
+ 0.1*sign(\link@lengthannotspacing)});
+ \path (0,\link@lengthannotspacing)
+ -- coordinate[pos=0.5] (coor) ++ (\link@length,0);
+ \node[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation node]
+ at (coor) (tag)
+ {\pgfkeysvalueof{/robotarm/annotations/labels/a\link@num}};
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation arrow]
+ (tag) -- (0,\link@lengthannotspacing);
+ \path[/robotarm/every annotation,
+ /robotarm/every length annotation,
+ /robotarm/every length annotation arrow]
+ (tag) -- (\link@length,\link@lengthannotspacing);
+
+ \pgfmathsetmacro\angleannotationcase{%
+ ifthenelse(\link@angle==0.0, 0, 1)}
+ \ifnum\angleannotationcase>0
+ % Angle annotation help lines
+ \path[/robotarm/every annotation,
+ /robotarm/every angle annotation,
+ /robotarm/every angle annotation help line]
+ (0,0) -- ++(-\link@angle:{\link@angleannotspacing+0.1});
+ \path[/robotarm/every annotation,
+ /robotarm/every angle annotation,
+ /robotarm/every angle annotation help line]
+ (0,0) -- ++(0:{\link@angleannotspacing+0.1});
+
+ % Angle annotation arrow
+ \path[/robotarm/every annotation,
+ /robotarm/every angle annotation,
+ /robotarm/every angle annotation arrow]
+ (0,0) ++ (-\link@angle:\link@angleannotspacing)
+ arc (-\link@angle:0:\link@angleannotspacing);
+
+ % Angle annotation node
+ \node[/robotarm/every annotation,
+ /robotarm/every angle annotation,
+ /robotarm/every angle annotation node]
+ at (-\link@angle/2:\link@angleannotspacing+0.3)
+ {\pgfkeysvalueof{/robotarm/annotations/labels/q\link@num}};
+ \fi
+ \fi
+ \end{scope}
+ }
+ \tikzset{
+ in link/.style={/robotarm/frames/in link #1},
+ in base link/.style={/robotarm/frames/in link 0},
+ in end effector/.style={/robotarm/frames/in end effector},
+ in world/.style={/robotarm/frames/in world},
+ }
+}
+\makeatother
+\endinput
+%%
+%% End of file `robotarm.sty'.
diff --git a/macros/luatex/generic/lua-widow-control/README.md b/macros/luatex/generic/lua-widow-control/README.md
index d9ccf7b454..920873c5b0 100644
--- a/macros/luatex/generic/lua-widow-control/README.md
+++ b/macros/luatex/generic/lua-widow-control/README.md
@@ -35,6 +35,11 @@ To load the package, add the relevant line to your preamble:
|OpTeX |`\load[lua-widow-control]` |
+Contributing
+------------
+
+Please see [`contributing.md`](https://github.com/gucci-on-fleek/lua-widow-control/blob/master/.github/contributing.md).
+
Licence
-------
@@ -43,4 +48,4 @@ Lua-widow-control is licensed under the [_Mozilla Public License_, version 2.0](
Please note that a compiled document is absolutely **not** considered to be an "Executable Form" as defined by the MPL. The use of lua-widow-control in a document does not place **any** obligations on the document's author or distributors. The MPL and CC-BY-SA licenses **only** apply to you if you distribute the lua-widow-control source code or documentation.
---
-_v1.1.6 (2022-02-22)_ <!--%%version %%date-->
+_v2.0.0 (2022-03-07)_ <!--%%version %%dashdate-->
diff --git a/macros/luatex/generic/lua-widow-control/lua-widow-control-2022-02-22.sty b/macros/luatex/generic/lua-widow-control/lua-widow-control-2022-02-22.sty
new file mode 100644
index 0000000000..783db59ed2
--- /dev/null
+++ b/macros/luatex/generic/lua-widow-control/lua-widow-control-2022-02-22.sty
@@ -0,0 +1,94 @@
+% lua-widow-control
+% https://github.com/gucci-on-fleek/lua-widow-control
+% SPDX-License-Identifier: MPL-2.0+
+% SPDX-FileCopyrightText: 2022 Max Chernoff
+
+\NeedsTeXFormat{LaTeX2e}[2015/01/01] % Formats built after 2015 include \LuaTeX{}Base
+\ProvidesPackage{lua-widow-control}%
+ [2022/02/22 v1.1.6]
+
+\PackageWarning{lua-widow-control}{%
+ Old LaTeX format detected!\MessageBreak\MessageBreak
+ Lua-widow-control prefers a LaTeX format\MessageBreak
+ newer than November 2020. I'll still run\MessageBreak
+ the latest Lua code, but I'm using an older\MessageBreak
+ version of the LaTeX code. This means that\MessageBreak
+ the key-value interface is *UNSUPPORTED*.\MessageBreak
+}
+
+\ifdefined\directlua\else
+ \PackageError{lua-widow-control}{%
+ LuaTeX is required for this package.\MessageBreak
+ Make sure to compile with `lualatex'%
+ }{}
+\fi
+
+\clubpenalty=1
+\widowpenalty=1
+\displaywidowpenalty=1
+
+% We can't use \\newlength since that makes a \TeX{} "skip", not a "dimen"
+\newdimen\lwcemergencystretch
+\lwcemergencystretch=3em
+
+\newcount\lwcmaxcost
+\lwcmaxcost=2147483647
+
+\directlua{require "lua-widow-control"}
+
+% Here, we enable font expansion/contraction. It isn't strictly necessary for
+% \lwc/'s functionality; however, it is required for the
+% lengthened paragraphs to not have terrible spacing.
+\RequirePackage{etoolbox}
+\AtEndPreamble{
+ \@ifpackageloaded{microtype}{}{ % Only load if not already loaded
+ \RequirePackage[
+ final,
+ activate={true,nocompatibility}
+ ]{microtype}
+ }
+}
+
+% Define \TeX{} wrappers for Lua functions
+\newcommand{\lwcenable}{\directlua{lwc.enable_callbacks()}}
+\newcommand{\lwcdisable}{\directlua{lwc.disable_callbacks()}}
+\newcommand{\iflwc}{\directlua{lwc.if_lwc_enabled()}}
+
+% Enable \lwc/ by default when the package is loaded.
+\lwcenable
+
+% Expansion of some parts of the document, such as section headings, is quite
+% undesirable, so we'll disable \lwc/ for certain commands.
+\newcommand{\lwc@patch@warning}[1]{\PackageWarning{lua-widow-control}{%
+ Patching the \protect#1 command failed%
+}}
+
+% We should only reenable \lwc/ at the end if it was already enabled.
+\newif\iflwc@should@reenable
+
+\newcommand{\lwc@patch@pre}{%
+ \iflwc%
+ \lwc@should@reenabletrue%
+ \lwcdisable%
+ \else%
+ \lwc@should@reenablefalse%
+ \fi%
+}
+
+\newcommand{\lwc@patch@post}{%
+ \iflwc@should@reenable%
+ \lwcenable%
+ \fi%
+}
+
+\newcommand{\lwcdisablecmd}[1]{%
+ \ifdefined#1
+ \pretocmd{#1}{\lwc@patch@pre}{}{\lwc@patch@warning{#1}}%
+ \apptocmd{#1}{\lwc@patch@post}{}{\lwc@patch@warning{#1}}%
+ \fi
+}
+
+\lwcdisablecmd{\@sect} % Sectioning
+
+
+\endinput
diff --git a/macros/luatex/generic/lua-widow-control/lua-widow-control.lua b/macros/luatex/generic/lua-widow-control/lua-widow-control.lua
index f1ecb54c65..5801feef12 100644
--- a/macros/luatex/generic/lua-widow-control/lua-widow-control.lua
+++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.lua
@@ -5,8 +5,24 @@
SPDX-FileCopyrightText: 2022 Max Chernoff
]]
-lwc = {}
+lwc = lwc or {}
lwc.name = "lua-widow-control"
+lwc.nobreak_behaviour = "keep"
+
+local write_nl = texio.write_nl
+local string_rep = string.rep
+local function debug_print(title, text)
+ if not lwc.debug then return end
+
+ local filler = 15 - #title
+
+ if text then
+ write_nl("log", "LWC (" .. title .. string_rep(" ", filler) .. "): " .. text)
+ else
+ write_nl("log", "LWC: " .. string_rep(" ", 18) .. title)
+ end
+end
+
--[[
\lwc/ is intended to be format-agonistic. It only runs on Lua\TeX{},
@@ -37,70 +53,90 @@ local last = node.slide
local copy = node.copy_list
local par_id = node.id("par") or node.id("local_par")
local glue_id = node.id("glue")
-local set_attribute = node.set_attribute
-local has_attribute = node.has_attribute
+local glyph_id = node.id("glyph")
+local penalty_id = node.id("penalty")
+local hlist_id = node.id("hlist")
+local traverse = node.traverse
+local set_attribute = node.set_attribute or node.setattribute
+local find_attribute = node.find_attribute or node.findattribute
local flush_list = node.flush_list or node.flushlist
local free = node.free
-local min_col_width = tex.sp("25em")
-local maxdimen = 1073741823 -- \\maxdimen in sp
+local min_col_width = tex.sp("250pt")
--[[
Package/module initialization
]]
-local warning, info, attribute, contrib_head, stretch_order, pagenum
-
-if context and lmtx then
- warning = logs.reporter("module", lwc.name)
- info = logs.reporter("module", lwc.name)
- attribute = attributes.public(lwc.name)
- contrib_head = 'contributehead' -- For \LuaMetaTeX{}
+local warning,
+ info,
+ attribute,
+ contrib_head,
+ stretch_order,
+ pagenum,
+ emergencystretch,
+ max_cost
+
+if lmtx then
+ debug_print("LMTX")
+ contrib_head = 'contributehead'
stretch_order = "stretchorder"
- pagenum = function() return tex.count["realpageno"] end
-elseif context and not lmtx then
+else
+ contrib_head = 'contrib_head'
+ stretch_order = "stretch_order"
+end
+
+if context then
+ debug_print("ConTeXt")
warning = logs.reporter("module", lwc.name)
info = logs.reporter("module", lwc.name)
attribute = attributes.public(lwc.name)
- contrib_head = 'contrib_head'
- stretch_order = "stretch_order"
pagenum = function() return tex.count["realpageno"] end
-elseif plain or latex then
- luatexbase.provides_module {
- name = lwc.name,
- date = "2022/02/22", --%%date
- version = "1.1.6", --%%version
- description = [[
-
- This module provides a LuaTeX-based solution to prevent
- widows and orphans from appearing in a document. It does
- so by increasing or decreasing the lengths of previous
- paragraphs.
- ]],
- }
- warning = function(str) luatexbase.module_warning(lwc.name, str) end
- info = function(str) luatexbase.module_info(lwc.name, str) end
- attribute = luatexbase.new_attribute(lwc.name)
- contrib_head = 'contrib_head' -- For \LuaTeX{}
- stretch_order = "stretch_order"
- pagenum = function() return tex.count[0] end
-elseif optex then
- local write_nl = texio.write_nl
- warning = function(str) write_nl(lwc.name .. " Warning: " .. str) end
- info = function(str) write_nl("log", lwc.name .. " Info: " .. str) end
- attribute = alloc.new_attribute(lwc.name)
- contrib_head = 'contrib_head'
- stretch_order = "stretch_order"
+ emergencystretch = "lwc_emergency_stretch"
+ max_cost = "lwc_max_cost"
+elseif plain or latex or optex then
pagenum = function() return tex.count[0] end
+
+ if tex.isdimen("g__lwc_emergencystretch_dim") then
+ emergencystretch = "g__lwc_emergencystretch_dim"
+ max_cost = "g__lwc_maxcost_int"
+ else
+ emergencystretch = "lwcemergencystretch"
+ max_cost = "lwcmaxcost"
+ end
+
+ if plain or latex then
+ debug_print("Plain/LaTeX")
+ luatexbase.provides_module {
+ name = lwc.name,
+ date = "2022/03/07", --%%slashdate
+ version = "2.0.0", --%%version
+ description = [[
+
+This module provides a LuaTeX-based solution to prevent
+widows and orphans from appearing in a document. It does
+so by increasing or decreasing the lengths of previous
+paragraphs.]],
+ }
+ warning = function(str) luatexbase.module_warning(lwc.name, str) end
+ info = function(str) luatexbase.module_info(lwc.name, str) end
+ attribute = luatexbase.new_attribute(lwc.name)
+ elseif optex then
+ debug_print("OpTeX")
+ warning = function(str) write_nl(lwc.name .. " Warning: " .. str) end
+ info = function(str) write_nl("log", lwc.name .. " Info: " .. str) end
+ attribute = alloc.new_attribute(lwc.name)
+ end
else -- uh oh
- error [[
- Unsupported format.
+ error [[Unsupported format.
- Please use (Lua)LaTeX, Plain (Lua)TeX, ConTeXt (MKXL/LMTX),
- or OpTeX.
- ]]
+Please use LaTeX, Plain TeX, ConTeXt or OpTeX.]]
end
local paragraphs = {} -- List to hold the alternate paragraph versions
+local function get_location()
+ return "At " .. pagenum() .. "/" .. #paragraphs
+end
+
--[[
Function definitions
@@ -165,11 +201,39 @@ local function register_callback(t)
end
end
+local function get_chars(head)
+ if not lwc.debug then return end
+
+ local chars = ""
+ for n in traverse(head) do
+ if n.id == glyph_id then
+ if n.char < 127 then
+ chars = chars .. string.char(n.char)
+ else
+ chars = chars .. "#"
+ end
+ elseif n.id == glue_id then
+ chars = chars .. " "
+ end
+ if #chars > 25 then
+ break
+ end
+ end
+
+ debug_print(get_location(), chars)
+end
+
+
+function lwc.paragraph_cost(demerits, lines)
+ return demerits / (2 * math.sqrt(lines))
+end
+
+
--- Saves each paragraph, but lengthened by 1 line
function lwc.save_paragraphs(head)
-- Prevent the "underfull hbox" warnings when we store a potential paragraph
local renable_box_warnings
- if (latex or plain) and
+ if (context or optex) or
#luatexbase.callback_descriptions("hpack_quality") == 0
then -- See #18 and michal-h21/linebreaker#3
renable_box_warnings = true
@@ -194,7 +258,7 @@ function lwc.save_paragraphs(head)
-- Break the paragraph 1 line longer than natural
local long_node, long_info = tex.linebreak(new_head, {
looseness = 1,
- emergencystretch = tex.dimen.lwcemergencystretch,
+ emergencystretch = tex.getdimen(emergencystretch),
})
-- Break the natural paragraph so we know how long it was
@@ -205,20 +269,31 @@ function lwc.save_paragraphs(head)
lwc.callbacks.disable_box_warnings.disable()
end
- -- If we can't lengthen the paragraph, assign a \emph{very} large demerit value
- local long_demerits
- if long_info.prevgraf == natural_info.prevgraf then
- long_demerits = maxdimen
- else
- long_demerits = long_info.demerits
- end
-
-- Offset the accumulated \\prevdepth
local prevdepth = node.new("glue")
prevdepth.width = natural_info.prevdepth - long_info.prevdepth
last(long_node).next = prevdepth
- table.insert(paragraphs, { demerits = long_demerits, node = long_node })
+ if long_info.prevgraf == natural_info.prevgraf + 1 then
+ table.insert(paragraphs, {
+ cost = lwc.paragraph_cost(long_info.demerits, long_info.prevgraf),
+ node = long_node
+ })
+ end
+
+ get_chars(head)
+ debug_print(get_location(), "nat lines " .. natural_info.prevgraf)
+ debug_print(
+ get_location(),
+ "nat cost " ..
+ lwc.paragraph_cost(natural_info.demerits, natural_info.prevgraf)
+ )
+ debug_print(get_location(), "long lines " .. long_info.prevgraf)
+ debug_print(
+ get_location(),
+ "long cost " ..
+ lwc.paragraph_cost(long_info.demerits, long_info.prevgraf)
+ )
-- \LuaMetaTeX{} crashes if we return `true`
return head
@@ -251,11 +326,12 @@ local function safe_last(head)
if ids[id] then
warning [[Circular node list detected!
-This should never happen. I'll try and recover, but your output may be
-corrupted. As a workaround, disable lua-widow-control for the
-affected paragraph or change the page breaks in your document.]]
-
+This should never happen. I'll try and
+recover, but your output may be corrupted.
+(Internal Error)]]
prev.next = nil
+ debug_print("safe_last", node.type(head.id) .. " " .. node.type(prev.id))
+
return prev
end
@@ -276,12 +352,16 @@ end
--- replace one paragraph with the same paragraph, but lengthened by one line.
--- Then, we can push the bottom line of the page to the next page.
function lwc.remove_widows(head)
+ local head_save = head -- Save the start of the `head` linked-list
+
local penalty = tex.outputpenalty - tex.interlinepenalty
local widowpenalty = tex.widowpenalty
local clubpenalty = tex.clubpenalty
local displaywidowpenalty = tex.displaywidowpenalty
local brokenpenalty = tex.brokenpenalty
+ debug_print("outputpenalty", tex.outputpenalty .. " " .. #paragraphs)
+
--[[
We only need to process pages that have orphans or widows. If `paragraphs`
is empty, then there is nothing that we can do.
@@ -289,7 +369,9 @@ function lwc.remove_widows(head)
The list of penalties is from:
https://tug.org/TUGboat/tb39-3/tb123mitt-widows-code.pdf#subsection.0.2.1
]]
- if (penalty == widowpenalty or
+ if penalty ~= 0 and
+ penalty < 10000 and
+ (penalty == widowpenalty or
penalty == displaywidowpenalty or
penalty == clubpenalty or
penalty == clubpenalty + widowpenalty or
@@ -302,28 +384,26 @@ function lwc.remove_widows(head)
penalty == brokenpenalty + clubpenalty + displaywidowpenalty) and
#paragraphs >= 1 then
else
+ paragraphs = {}
return head
end
info("Widow/orphan detected. Attempting to remove.")
- local head_save = head -- Save the start of the `head` linked-list
-
--[[
- Find the paragraph on the page with the minimum penalty.
-
- This would be a 1-liner in Python or JavaScript, but Lua is pretty low-level,
- so there's quite a bit of code here.
+ Find the paragraph on the page with the least cost.
]]
local paragraph_index = 1
- local minimum_demerits = paragraphs[paragraph_index].demerits
+ local best_cost = paragraphs[paragraph_index].cost
-- We find the current "best" replacement, then free the unused ones
for i, paragraph in pairs(paragraphs) do
- if paragraph.demerits < minimum_demerits and i <= #paragraphs - 1 then
+ if paragraph.cost < best_cost and i <= #paragraphs - 1 then
+ -- Clear the old best paragraph
flush_list(paragraphs[paragraph_index].node)
paragraphs[paragraph_index].node = nil
- paragraph_index, minimum_demerits = i, paragraph.demerits
+ -- Set the new best paragraph
+ paragraph_index, best_cost = i, paragraph.cost
elseif i > 1 then
-- Not sure why `i > 1` is required?
flush_list(paragraph.node)
@@ -331,50 +411,113 @@ function lwc.remove_widows(head)
end
end
+ debug_print(
+ "selected para",
+ pagenum() ..
+ "/" ..
+ paragraph_index ..
+ " (" ..
+ best_cost ..
+ ")"
+ )
+
+ if best_cost > tex.getcount(max_cost) then
+ -- If the best replacement is too bad, we can't do anything
+ warning("Widow/Orphan NOT removed on page " .. pagenum())
+ paragraphs = {}
+ return head_save
+ end
+
local target_node = paragraphs[paragraph_index].node
- local clear_flag = false
- -- Loop through all of the nodes on the page
+ -- Start of final paragraph
+ debug_print("remove_widows", "moving last line")
+
+ head = last(head_save).prev
+ local big_penalty_found, last_line, hlist_head
+ while head do
+ if head.id == glue_id then
+ -- Ignore any glue nodes
+ elseif head.id == penalty_id and head.penalty >= 10000 then
+ -- Infinite break penalty
+ big_penalty_found = true
+ elseif big_penalty_found and head.id == hlist_id then
+ -- Line before the penalty
+ if lwc.nobreak_behaviour == "keep" then
+ hlist_head = head
+ big_penalty_found = false
+ elseif lwc.nobreak_behaviour == "split" then
+ head = last(head_save)
+ break
+ elseif lwc.nobreak_behaviour == "warn" then
+ warning("Widow/Orphan NOT removed on page " .. pagenum())
+ paragraphs = {}
+ return head_save
+ end
+ else
+ -- Not found
+ if hlist_head then
+ head = hlist_head
+ else
+ head = last(head_save)
+ end
+ break
+ end
+ head = head.prev
+ end
+
+ last_line = copy(head)
+ last(last_line).next = copy(tex.lists[contrib_head])
+
+ head.prev.prev.next = nil
+ -- Move the last line to the next page
+ tex.lists[contrib_head] = last_line
+
+ local free_next_nodes = false
+
+ -- Loop through all of the nodes on the page with the lwc attribute
+ head = head_save
while head do
+ local value
+ value, head = find_attribute(head, attribute)
+
+ if not head then
+ break
+ end
+
+ debug_print("remove_widows", "found " .. value)
+
-- Insert the start of the replacement paragraph
- if has_attribute(head, attribute, paragraph_index) then
+ if value == paragraph_index then
+ debug_print("remove_widows", "replacement start")
safe_last(target_node) -- Remove any loops
head.prev.next = target_node
- clear_flag = true
+ free_next_nodes = true
end
-- Insert the end of the replacement paragraph
- if has_attribute(head, attribute, -1 * paragraph_index) then
+ if value == -1 * paragraph_index then
+ debug_print("remove_widows", "replacement end")
safe_last(target_node).next = head.next
- clear_flag = false
+ break
end
- -- Start of final paragraph
- if has_attribute(head, attribute, #paragraphs) then
- local last_line = copy(last(head))
-
- last(last_line).next = copy(tex.lists[contrib_head])
-
- last(head).prev.prev.next = nil
- -- Move the last line to the next page
- tex.lists[contrib_head] = last_line
- info(
- "Widow/orphan successfully removed at paragraph "
- .. paragraph_index
- .. " on page "
- .. pagenum()
- .. "."
- )
- end
-
- if clear_flag then
+ if free_next_nodes then
head = free(head)
else
head = head.next
end
end
+ info(
+ "Widow/orphan successfully removed at paragraph "
+ .. paragraph_index
+ .. " on page "
+ .. pagenum()
+ .. "."
+ )
+
paragraphs = {} -- Clear paragraphs array at the end of the page
return head_save
@@ -413,18 +556,19 @@ lwc.callbacks = {
local enabled = false
function lwc.enable_callbacks()
+ debug_print("callbacks", "enabling")
if not enabled then
- lwc.callbacks.remove_widows.enable()
lwc.callbacks.save_paragraphs.enable()
lwc.callbacks.mark_paragraphs.enable()
enabled = true
else
- warning("Already enabled")
+ info("Already enabled")
end
end
function lwc.disable_callbacks()
+ debug_print("callbacks", "disabling")
if enabled then
lwc.callbacks.save_paragraphs.disable()
lwc.callbacks.mark_paragraphs.disable()
@@ -436,11 +580,12 @@ function lwc.disable_callbacks()
enabled = false
else
- warning("Already disabled")
+ info("Already disabled")
end
end
function lwc.if_lwc_enabled()
+ debug_print("iflwc")
if enabled then
tex.sprint("\\iftrue")
else
@@ -448,4 +593,49 @@ function lwc.if_lwc_enabled()
end
end
+--- Silence the luatexbase "Enabling/Removing <callback>" info messages
+---
+--- Every time that a paragraph is typeset, \lwc/ hooks in
+--- and typesets the paragraph 1 line longer. Some of these longer paragraphs
+--- will have pretty bad badness values, so TeX will issue an over/underfull
+--- hbox warning. To block these warnings, we hook into the `hpack_quality`
+--- callback and disable it so that no warning is generated.
+---
+--- However, each time that we enable/disable the null `hpack_quality` callback,
+--- luatexbase puts an info message in the log. This completely fills the log file
+--- with useless error messages, so we disable it here.
+---
+--- This uses the Lua `debug` library to internally modify the log upvalue in the
+--- `add_to_callback` function. This is almost certainly a terrible idea, but I don't
+--- know of a better way.
+local function silence_luatexbase()
+ local nups = debug.getinfo(luatexbase.add_to_callback).nups
+
+ for x = 1, nups do
+ local name, func = debug.getupvalue(luatexbase.add_to_callback, x)
+ if name == "luatexbase_log" then
+ debug.setupvalue(
+ luatexbase.add_to_callback,
+ x,
+ function(text)
+ if text:match("^Inserting") or text:match("^Removing") then
+ return
+ else
+ func(text)
+ end
+ end
+ )
+ return
+ end
+ end
+end
+
+
+-- Activate \lwc/
+if plain or latex then
+ silence_luatexbase()
+end
+
+lwc.callbacks.remove_widows.enable()
+
return lwc
diff --git a/macros/luatex/generic/lua-widow-control/lua-widow-control.opm b/macros/luatex/generic/lua-widow-control/lua-widow-control.opm
index 4b8837cfec..992d817a5c 100644
--- a/macros/luatex/generic/lua-widow-control/lua-widow-control.opm
+++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.opm
@@ -3,17 +3,18 @@
% SPDX-License-Identifier: MPL-2.0+
% SPDX-FileCopyrightText: 2022 Max Chernoff
-\_codedecl\lwcenable{lua-widow-control <v1.1.6>} %%version
+\_codedecl\lwcenable{lua-widow-control <v2.0.0>} %%version
\_clubpenalty=1
\_widowpenalty=1
-\_displaywidowpenalty=0
-\_interlinepenalty=0
-\_brokenpenalty=0
+\_displaywidowpenalty=1
\_newdimen\lwcemergencystretch
\lwcemergencystretch=3em
+\_newcount\lwcmaxcost
+\lwcmaxcost=2147483647
+
\_directlua{require "lua-widow-control"}
% Define \TeX{} wrappers for Lua functions
diff --git a/macros/luatex/generic/lua-widow-control/lua-widow-control.pdf b/macros/luatex/generic/lua-widow-control/lua-widow-control.pdf
index 4560ff7c56..9243a3a07c 100644
--- a/macros/luatex/generic/lua-widow-control/lua-widow-control.pdf
+++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.pdf
Binary files differ
diff --git a/macros/luatex/generic/lua-widow-control/lua-widow-control.sty b/macros/luatex/generic/lua-widow-control/lua-widow-control.sty
index 9ef5a76de5..56462e40d8 100644
--- a/macros/luatex/generic/lua-widow-control/lua-widow-control.sty
+++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.sty
@@ -3,82 +3,232 @@
% SPDX-License-Identifier: MPL-2.0+
% SPDX-FileCopyrightText: 2022 Max Chernoff
-\NeedsTeXFormat{LaTeX2e}[2015/01/01] % Formats built after 2015 include \LuaTeX{}Base
-\ProvidesPackage{lua-widow-control}%
- [2022/02/22 v1.1.6] %%version %%date
+% Formats built after 2015 include \LuaTeX{}Base, so this is the absolute
+% minimum version that we will run under.
+\NeedsTeXFormat{LaTeX2e}[2015/01/01]
+
+% For _really_ old formats
+\providecommand\DeclareRelease[3]{}
+\providecommand\DeclareCurrentRelease[2]{}
+
+\DeclareRelease{}{0000-00-00}{lua-widow-control-2022-02-22.sty}
+\DeclareRelease{v1.1.6}{2022-02-22}{lua-widow-control-2022-02-22.sty}
+\DeclareCurrentRelease{v2.0.0}{2022-03-07} %%version %%dashdate
+
+% If this version of LaTeX doesn't support command hooks, then we load
+% the last v1.1.X version of the package.
+\providecommand\IfFormatAtLeastTF{\@ifl@t@r\fmtversion}
+\IfFormatAtLeastTF{2020/10/01}{}{\input{lua-widow-control-2022-02-22.sty}}
+\IfFormatAtLeastTF{2020/10/01}{}{\endinput}
+
+\ProvidesExplPackage
+ {lua-widow-control}
+ {2022/03/07} %%slashdate
+ {v2.0.0} %%version
+ {Use Lua to remove widows and orphans}
+
+% Unconditional Package Loads
+\RequirePackage { l3keys2e }
+
+% Message and String Constants
+\str_const:Nn \c__lwc_name_str { lua-widow-control }
+
+\msg_new:nnn
+ { \c__lwc_name_str }
+ { no-luatex }
+ {
+ LuaTeX~ is~ REQUIRED! \\
+ Make~ sure~ to~ compile~ your~ document~ with~ `lualatex'.
+ }
-\ifdefined\directlua\else
- \PackageError{lua-widow-control}{%
- LuaTeX is required for this package.\MessageBreak
- Make sure to compile with `lualatex'%
- }{}
-\fi
+\msg_new:nnn
+ { \c__lwc_name_str }
+ { patch-failed }
+ {
+ Patching~ \c_backslash_str #1~ failed. \\
+ Please~ ensure~ that~ \c_backslash_str #1~ exists.
+ }
-\clubpenalty=1
-\widowpenalty=1
-\displaywidowpenalty=0
-\interlinepenalty=0
-\brokenpenalty=0
+\msg_new:nnn
+ { \c__lwc_name_str }
+ { old-format-patch }
+ {
+ Patching~ not~ supported~ with~ old~ LaTeX. \\
+ Please~ use~ a~ LaTeX~ format~ >=~ 2021/06/01.
+ }
-% We can't use \\newlength since that makes a \TeX{} "skip", not a "dimen"
-\newdimen\lwcemergencystretch
-\lwcemergencystretch=3em
+\msg_new:nnn
+ { \c__lwc_name_str }
+ { old-command }
+ {
+ \c_backslash_str #1~ has~ been~ REMOVED! \\
+ Please~ use~ \c_backslash_str setuplwc \c_left_brace_str #2
+ \c_right_brace_str\ instead.
+ }
-\directlua{require "lua-widow-control"}
+% Don't let the user proceed unless they are using \LuaTeX{}.
+\sys_if_engine_luatex:F {
+ \msg_critical:nn { \c__lwc_name_str } { no-luatex }
+}
+
+% Define (most of) the keys
+\keys_define:nn { \c__lwc_name_str } {
+ emergencystretch .dim_gset:N = \g__lwc_emergencystretch_dim,
+ emergencystretch .value_required:n = true,
+ emergencystretch .initial:x = \dim_max:nn { 3em } { 30pt },
+
+ max-cost .int_gset:N = \g__lwc_maxcost_int,
+ max-cost .value_required:n = true,
+ max-cost .initial:x = \c_max_int,
+
+ widowpenalty .code:n = \int_gset:Nn \tex_widowpenalty:D { #1 }
+ \int_gset:Nn \tex_displaywidowpenalty:D { #1 },
+ widowpenalty .value_required:n = true,
+ widowpenalty .initial:n = 1,
+
+ orphanpenalty .code:n = \int_gset:Nn \tex_clubpenalty:D { #1 }
+ \int_gset:Nn \@clubpenalty { #1 },
+ orphanpenalty .value_required:n = true,
+ orphanpenalty .initial:n = 1,
+
+ microtype .bool_gset:N = \g__lwc_use_microtype_bool,
+ microtype .value_required:n = true,
+ microtype .initial:n = true,
+ microtype .usage:n = preamble,
+
+ disablecmds .clist_gset:N = \g__lwc_disablecmds_cl,
+ disablecmds .value_required:n = false,
+ disablecmds .initial:n = { \@sect },
+ disablecmds .usage:n = preamble,
+
+ nobreak .choice:,
+ nobreak / keep .code:n = \lua_now:n { lwc.nobreak_behaviour = "keep" },
+ nobreak / split .code:n = \lua_now:n { lwc.nobreak_behaviour = "split" },
+ nobreak / warn .code:n = \lua_now:n { lwc.nobreak_behaviour = "warn" },
+}
+
+% Load the Lua code
+\lua_now:n { require "lua-widow-control" }
% Here, we enable font expansion/contraction. It isn't strictly necessary for
% \lwc/'s functionality; however, it is required for the
-% lengthened paragraphs to not have terrible spacing.
-\RequirePackage{etoolbox}
-\AtEndPreamble{
- \@ifpackageloaded{microtype}{}{ % Only load if not already loaded
- \RequirePackage[
- final,
- activate={true,nocompatibility}
- ]{microtype}
+% lengthened paragraphs to not have terrible spacing.
+\bool_if:NT \g__lwc_use_microtype_bool {
+ \hook_gput_code:nnn { begindocument / before } { \c__lwc_name_str } {
+ \@ifpackageloaded { microtype } {} {
+ \RequirePackage[
+ final,
+ activate = { true, nocompatibility }
+ ]
+ { microtype }
+ }
}
}
-% Define \TeX{} wrappers for Lua functions
-\newcommand{\lwcenable}{\directlua{lwc.enable_callbacks()}}
-\newcommand{\lwcdisable}{\directlua{lwc.disable_callbacks()}}
-\newcommand{\iflwc}{\directlua{lwc.if_lwc_enabled()}}
+% Core Function Definitions
+\cs_new:Npn \__lwc_enable: {
+ \lua_now:n { lwc.enable_callbacks() }
+}
+
+\cs_new:Npn \__lwc_disable: {
+ \lua_now:n { lwc.disable_callbacks() }
+}
-% Enable \lwc/ by default when the package is loaded.
-\lwcenable
+\prg_set_conditional:Nnn \__lwc_if_enabled: { T, F, TF } {
+ \lua_now:n { lwc.if_lwc_enabled() }
+ \prg_return_true:
+ \else
+ \prg_return_false:
+ \fi
+}
% Expansion of some parts of the document, such as section headings, is quite
% undesirable, so we'll disable \lwc/ for certain commands.
-\newcommand{\lwc@patch@warning}[1]{\PackageWarning{lua-widow-control}{%
- Patching the \protect#1 command failed%
-}}
-
-% We should only reenable \lwc/ at the end if it was already enabled.
-\newif\iflwc@should@reenable
-
-\newcommand{\lwc@patch@pre}{%
- \iflwc%
- \lwc@should@reenabletrue%
- \lwcdisable%
- \else%
- \lwc@should@reenablefalse%
- \fi%
+\bool_new:N \g__lwc_should_reenable_bool
+
+\cs_new:Npn \__lwc_patch_cmd:c #1 {
+ \IfFormatAtLeastTF {2021/06/01 } {
+ \hook_gput_code:nnn { cmd / #1 / before } { \c__lwc_name_str } {
+ % We should only reenable \lwc/ at the end if it was already enabled.
+ \__lwc_if_enabled:TF {
+ \bool_gset_true:N \g__lwc_should_reenable_bool
+ \__lwc_disable:
+ } {
+ \bool_gset_false:N \g__lwc_should_reenable_bool
+ }
+ }
+ \hook_gput_code:nnn { cmd / #1 / after } { \c__lwc_name_str } {
+ \bool_if:NT { \g__lwc_should_reenable_bool } {
+ \__lwc_enable:
+ }
+ }
+ } {
+ \msg_warning:nn
+ { \c__lwc_name_str }
+ { old-format-patch }
+ }
}
-\newcommand{\lwc@patch@post}{%
- \iflwc@should@reenable%
- \lwcenable%
- \fi%
+\cs_new:Npn \__lwc_patch_cmd:N #1 {
+ \__lwc_patch_cmd:c { \cs_to_str:N #1 }
}
-\newcommand{\lwcdisablecmd}[1]{%
- \ifdefined#1
- \pretocmd{#1}{\lwc@patch@pre}{}{\lwc@patch@warning{#1}}%
- \apptocmd{#1}{\lwc@patch@post}{}{\lwc@patch@warning{#1}}%
- \fi
+\hook_gput_code:nnn { begindocument / before } { \c__lwc_name_str } {
+ \clist_map_function:NN \g__lwc_disablecmds_cl \__lwc_patch_cmd:N
+}
+
+% Define some final keys
+\keys_define:nn { \c__lwc_name_str } {
+ enable .choice:,
+ enable / true .code:n = \__lwc_enable:,
+ enable / false .code:n = \__lwc_disable:,
+ enable .initial:n = true,
+ enable .default:n = true,
+ enable .value_required:n = false,
+
+ disable .code:n = \__lwc_disable:,
+ disable .value_forbidden:n = true,
+
+ debug .choice:,
+ debug / true .code:n = \lua_now:n { lwc.debug = true },
+ debug / false .code:n = \lua_now:n { lwc.debug = false },
+
+ strict .meta:n = { emergencystretch = 0pt,
+ max-cost = 2500,
+ nobreak = warn,
+ },
+ strict .value_forbidden:n = true,
+
+ default .meta:n = { emergencystretch = 3em,
+ max-cost = \c_max_int,
+ nobreak = keep,
+ },
+ default .value_forbidden:n = true,
}
-\lwcdisablecmd{\@sect} % Sectioning
+% Add the user interface for the keys
+\ProcessKeysPackageOptions{ \c__lwc_name_str }
+
+\NewDocumentCommand \lwcsetup {m} { \keys_set:nn{\c__lwc_name_str}{#1} }
+
+% Legacy Commands
+\NewDocumentCommand \lwcemergencystretch { } {
+ \msg_error:nnnnn
+ { \c__lwc_name_str }
+ { old-command }
+ { lwcemergencystretch }
+ { emergencystretch=XXXpt }
+}
+
+\NewDocumentCommand \lwcdisablecmd { m } {
+ \msg_error:nnxx
+ { \c__lwc_name_str }
+ { old-command }
+ { lwcdisablecmd }
+ { disablecmds={\c_backslash_str aaa,~ \c_backslash_str bbb} }
+}
+\cs_new_eq:NN \lwcenable \__lwc_enable:
+\cs_new_eq:NN \lwcdisable \__lwc_disable:
\endinput
diff --git a/macros/luatex/generic/lua-widow-control/lua-widow-control.tex b/macros/luatex/generic/lua-widow-control/lua-widow-control.tex
index d83e6114f4..7a75e52132 100644
--- a/macros/luatex/generic/lua-widow-control/lua-widow-control.tex
+++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.tex
@@ -3,7 +3,7 @@
% SPDX-License-Identifier: MPL-2.0+
% SPDX-FileCopyrightText: 2022 Max Chernoff
-\wlog{lua-widow-control v1.1.6} %%version
+\wlog{lua-widow-control v2.0.0} %%version
\ifx\directlua\undefined
\errmessage{%
@@ -16,13 +16,14 @@
\clubpenalty=1
\widowpenalty=1
-\displaywidowpenalty=0
-\interlinepenalty=0
-\brokenpenalty=0
+\displaywidowpenalty=1
\newdimen\lwcemergencystretch
\lwcemergencystretch=3em
+\newcount\lwcmaxcost
+\lwcmaxcost=2147483647
+
\directlua{require "lua-widow-control"}
% Here, we enable font expansion/contraction. It isn't strictly necessary for
diff --git a/macros/luatex/generic/lua-widow-control/lwc-documentation.mkxl b/macros/luatex/generic/lua-widow-control/lwc-documentation.mkxl
index 0256bdd56a..4edaa10694 100644
--- a/macros/luatex/generic/lua-widow-control/lwc-documentation.mkxl
+++ b/macros/luatex/generic/lua-widow-control/lwc-documentation.mkxl
@@ -138,6 +138,8 @@ setbounds currentpicture to boundingbox OverlayBox;
after={\blank[medium]}
]
+\define[1]\meta{\m{\langle}\italic{#1}\m{\rangle}}
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% Section Commands %%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -183,6 +185,7 @@ setbounds currentpicture to boundingbox OverlayBox;
\stopcurrentlistentrywrapper
\par
+ \nobreak
\setbox0=\vbox{\framedtext[ % Align the subsection titles under the section title
frame=off,
@@ -199,6 +202,8 @@ setbounds currentpicture to boundingbox OverlayBox;
\else
\box0
\fi
+
+ \goodbreak
\stoptexdefinition
\newif\iffirstsubsection
diff --git a/macros/luatex/generic/lua-widow-control/lwc-documentation.tex b/macros/luatex/generic/lua-widow-control/lwc-documentation.tex
index bbee53cee1..1194acb44f 100644
--- a/macros/luatex/generic/lua-widow-control/lwc-documentation.tex
+++ b/macros/luatex/generic/lua-widow-control/lwc-documentation.tex
@@ -24,6 +24,7 @@
\def\waos/{widows and orphans}
\def\wao/{widow and orphan}
\def\woo/{widow or orphan}
+\def\plainop/{Plain~\TeX{}/Op\TeX{}}
\let\q=\quotation
\def\titlecite[#1]{\cite[title][#1]\cite[#1]}
@@ -37,7 +38,7 @@
\startdocument[
title=lua-widow-control,
author=Max Chernoff,
- version=1.1.6, %%version
+ version=2.0.0, %%version
github=https://github.com/gucci-on-fleek/lua-widow-control,
ctan=https://www.ctan.org/pkg/lua-widow-control,
]
@@ -45,7 +46,7 @@
\Lwc/ is a Plain~\TeX/\LaTeX/\ConTeXt{}/Op\TeX{} package that removes \waos/ without any user intervention. Using the power of \LuaTeX{}, it does so \emph{without} stretching any glue or shortening any pages or columns. Instead, \lwc/ automatically lengthens a paragraph on a page or column where a \woo/ would otherwise occur.
\section{Quick Start}
-Ensure that your \TeX~Live/Mik\TeX{} distribution is up-to-date. Then, \LaTeX{} users just need to place \inlineTEX{\usepackage{lua-widow-control}} in the preamble of your document. For more details, see the \goto{Installation and Usage sections}[sec:install].
+Ensure that your \TeX~Live/Mik\TeX{} distribution is up-to-date. Then, \LaTeX{} users just need to place \inlineTEX{\usepackage{lua-widow-control}} in the preamble of your document. For more details, see the \goto{Usage sections}[sec:usage].
\subject{Contents}
\placecontent[criterium=all]
@@ -157,7 +158,7 @@ To do so, \lwc/ lengthened the second paragraph by one line. If you look closely
\Lwc/ automatically finds the \q{best} paragraph to stretch, so the increase in interword spaces should almost always be minimal.
-\section[sec:install]{Installation}
+\section{Installation}
Most up-to-date \TeX~Live and Mik\TeX{} systems should already have \lwc/ installed. However, a manual installation may occasionally be required.
\subsection{\TeX~Live}
@@ -190,7 +191,11 @@ Currently, \ConTeXt{} \acronym{MKXL} (\LuaMetaTeX{}) users must manually install
\Lwc/ requires \LuaTeX{} ($\ge$ 0.85) and the most recent version of \sans{luatexbase} (2015/10/04). Any version of \TeX~Live $\ge$ 2016 will meet these requirements.
\subsection{\LaTeX{}}
-\Lwc/ requires \LuaTeX{} ($\ge$ 0.85), \LaTeX{} ($\ge$ 2015/01/01), \sans{microtype} (any version), and \sans{etoolbox} (any version). Any version of \TeX~Live $\ge$ 2016 will meet these requirements.
+\Lwc/ requires \LuaTeX{} ($\ge$ 0.85), \LaTeX{} ($\ge$ 2020/10/01), and \sans{microtype} (any version). Any version of \TeX~Live $\ge$ 2021 will meet these requirements.
+
+\Lwc/ also supports a \q{legacy} mode for older \LaTeX{} kernels. This uses an older version of the \LaTeX{} code while still using the most recent Lua code. This mode requires \LuaTeX{} ($\ge$ 0.85), \LaTeX{} ($\ge$ 2015/01/01), \sans{microtype} (any version), and \sans{etoolbox} (any version). Any version of \TeX~Live $\ge$ 2016 will meet these requirements.
+
+Please note that when running in legacy mode, you cannot use the key--value interface. Instead, you should follow the \q{Plain~\TeX{}} interface.
\subsection{\ConTeXt{}}
\Lwc/ supports both \ConTeXt{} \acronym{MKXL} (\LuaMetaTeX{}) and \ConTeXt{} \acronym{MKIV} (\LuaTeX{}).
@@ -199,88 +204,221 @@ Currently, \ConTeXt{} \acronym{MKXL} (\LuaMetaTeX{}) users must manually install
\Lwc/ works with any version of Op\TeX{} and has no dependencies.
-\section{Loading the Package}
-
-\subsection{Plain \TeX}
-
-\inlineTEX{\input lua-widow-control}
+\section[sec:usage]{Loading the Package}
-\subsection{\LaTeX}
+\setupTABLE[frame=off]
+\setupTABLE[column][first][roffset=1.5em]
+\setupTABLE[row][first][toffset=0.25ex]
-\inlineTEX{\usepackage{lua-widow-control}}
+\startTABLE
+ \NC Plain~\TeX{}
+ \NC\inlineTEX{\input lua-widow-control}
+ \NC\NR
+ \NC \LaTeX{}
+ \NC\inlineTEX{\usepackage{lua-widow-control}}
+ \NC\NR
+ \NC \ConTeXt{}
+ \NC\inlineTEX{\usemodule[lua-widow-control]}
+ \NC\NR
+ \NC Op\TeX{}
+ \NC\inlineTEX{\load[lua-widow-control]}
+ \NC\NR
+\stopTABLE
-\subsection{\ConTeXt}
+\section{Options}
-\inlineTEX{\usemodule[lua-widow-control]}
+\Lwc/ is automatically enabled with the default settings as soon as you load it. Most users should not need to configure \lwc/; however, the packages provides a few commands.
-\subsection{Op\TeX{}}
+\subsection{Overview}
-\inlineTEX{\load[lua-widow-control]}
+\LaTeX{} users can set the options either when loading the package (\tex{usepackage[\meta{options}]\{lua-widow-control\}}) or at any point using \tex{lwcsetup\{\meta{options}\}}.
-\section{Columns}
+\ConTeXt{} users should always use the \tex{setuplwc[\meta{options}]} command.
-Since \TeX{} implements column breaking and page breaking through the same internal mechanisms, \lwc/ should remove \waos/ between columns just as well as it does with \waos/ between pages. This has been tested with the standard \LaTeX{} class option \type{twocolumn} and the two-column output routine from Chapter~23 of \cite[title][texbook].\cite[texbook] \Lwc/ should presumably work with any other multi-column implementation; however, due to the diversity and complexity of output~routines, this cannot be guaranteed.
+Plain~\TeX{} and Op\TeX{} are a little different. Some options have commands provided (i.e., \tex{lwcemergencystretch=\meta{dimension}}), while others must be set manually (i.e., \tex{directlua\{lwc.debug = true\}}).
-\section{Advanced Usage}
+Also, please note that not all commands are provided for all formats.
-\Lwc/ is automatically enabled with the default settings as soon as you load it. Most users should not need to configure \lwc/; however, the packages does provide a few commands.
+\subsection{Enabling}
-\setupTABLE[frame=off]
+\Lwc/ is enabled by default as soon as you load it. Nevertheless, you may need to explicitly reenable it if you have previously disabled it.
-\subsection{Plain \TeX{}}
\startTABLE
-\NC Enable (default) \NC \inlineTEX{\lwcenable} \NC\NR
-\NC Disable \NC \inlineTEX{\lwcdisable} \NC\NR
-\NC \estretch/ \NC \inlineTEX{\lwcemergencystretch=3em} \NC\NR
-\NC Selectively Disable \NC \inlineTEX{\lwcdisablecmd{\cmd}} \NC\NR
+ \NC \plainop/
+ \NC\inlineTEX{\lwcenable}
+ \NC\NR
+ \NC \LaTeX{}
+ \NC\inlineTEX{\lwcsetup{enable}}
+ \NC\NR
+ \NC \ConTeXt{}
+ \NC\inlineTEX{\setuplwc[state = start]}
+ \NC\NR
\stopTABLE
-\subsection{\LaTeX{}}
-\startTABLE
-\NC Enable (default) \NC \inlineTEX{\lwcenable} \NC\NR
-\NC Disable \NC \inlineTEX{\lwcdisable} \NC\NR
-\NC \estretch/ \NC \inlineTEX{\setlength{\lwcemergencystretch}{3em}} \NC\NR
-\NC Selectively Disable \NC \inlineTEX{\lwcdisablecmd{\cmd}} \NC\NR
-\stopTABLE
+\subsection{Disabling}
-\subsection{\ConTeXt{}}
\startTABLE
-\NC Enable (default) \NC \inlineTEX{\setuplwc[state = start]} \NC\NR
-\NC Disable \NC \inlineTEX{\setuplwc[state = stop]} \NC\NR
-\NC \estretch/ \NC \inlineTEX{\setuplwc[emergencystretch = 3em]} \NC\NR
-\NC Selectively Disable \NC \inlineTEX{\prependtoks\lwc@patch@pre \to\everybeforefoo} \NC\NR
-\NC \NC \inlineTEX{\prependtoks\lwc@patch@post\to\everyafterfoo} \NC\NR
+ \NC \plainop/
+ \NC\inlineTEX{\lwcdisable}
+ \NC\NR
+ \NC \LaTeX{}
+ \NC\inlineTEX{\lwcsetup{disable}}
+ \NC\NR
+ \NC \ConTeXt{}
+ \NC\inlineTEX{\setuplwc[state = stop]}
+ \NC\NR
\stopTABLE
-\subsection{Op\TeX{}}
+\subsection{Strict Mode}
+
+By default, \lwc/ takes all possible measures to remove \waos/. This normally works out pretty well; however, sometimes these measures may be a little more aggressive than certain users want.
+
+\Lwc/ offers a \q{strict} mode that will only make modification to the page that are near-imperceptible to remove \waos/.
+
\startTABLE
-\NC Enable (default) \NC \inlineTEX{\lwcenable} \NC\NR
-\NC Disable \NC \inlineTEX{\lwcdisable} \NC\NR
-\NC \estretch/ \NC \inlineTEX{\lwcemergencystretch=3em} \NC\NR
-\NC Selectively Disable \NC \emph{Not Implemented} \NC\NR
+ \NC \LaTeX{}
+ \NC\inlineTEX{\lwcsetup{strict}}
+ \NC\NR
\stopTABLE
+Internally, this sets {\tt emergencystretch = 0pt}, {\tt max-cost = 2500}, and {\tt nobreak = warn}.
+
\subsection{\estretch/}
You can configure the \estretch/ used when stretching a paragraph. The default value is 3~em.
-\Lwc/ will only use use the \estretch/ when it cannot lengthen a paragraph in any other way, so it is fairly safe to set this to a large value. \TeX{} still accumulates badness when \estretch/ is used, so its pretty rare that a paragraph that requires any \estretch/ will actually be used on the page.
+\Lwc/ will only use the \estretch/ when it cannot lengthen a paragraph in any other way, so it is fairly safe to set this to a large value. \TeX{} still accumulates badness when \estretch/ is used, so it's pretty rare that a paragraph that requires any \estretch/ will actually be used on the page.
+
+\startTABLE
+ \NC \plainop/
+ \NC\tex{lwcemergencystretch=\meta{dimension}}
+ \NC\NR
+ \NC \LaTeX{}
+ \NC\tex{lwcsetup\{emergencystretch = \meta{dimension}\}}
+ \NC\NR
+ \NC \ConTeXt{}
+ \NC\tex{setuplwc[emergencystretch = \meta{dimension}]}
+ \NC\NR
+\stopTABLE
\subsection{Selectively Disabling}
Sometimes, you may want to disable \lwc/ for certain commands where stretching is undesirable. For example, you typically wouldn't want section headings to be stretched.
-You could just disable then reenable \lwc/ every time that you use the command; however, \lwc/ provides a convenience macro that will do this automatically for you. Place \inlineTEX{\lwcdisablecmd{\cmd}} in the preamble, and \lwc/ will not expand any arguments of \tex{cmd} in the document.
+You could just disable then reenable \lwc/ every time that you use the command; however, \lwc/ provides a convenience macro that will do this automatically for you.
+
+\Lwc/ automatically patches the default \LaTeX{}, \ConTeXt{}, and Plain~\TeX{} section commands, so you shouldn't need to patch these yourself; however, \lwc/ does \bold{not} patch the non-standard section commands provided by \sans{memoir}, \sans{\acronym{KOMA}-script}, \sans{titlesec}, Op\TeX{}, and others. You'll need to add these yourself.
+
+\startTABLE
+ \NC Plain \TeX{}
+ \NC \tex{lwcdisablecmd\{\meta{\backslash macro}\}}
+ \NC\NR
+ \NC \LaTeX{}
+ \NC \tex{lwcsetup\{disablecmds = \{\meta{\backslash macroone}, \meta{\backslash macrotwo}\}\}}
+ \NC\NR
+ \NC \ConTeXt{}
+ \NC \tex{prependtoks\backslash lwc@patch@pre\backslash to\backslash everybefore\meta{hook}}
+ \NC\NR\NC\NC \tex{prependtoks\backslash lwc@patch@post\backslash to\backslash everyafter\meta{hook}}
+ \NC\NR
+\stopTABLE
+
+\subsection{Widow and Orphan Penalties}
+
+You can also manually adjust the penalties that \TeX{} assigns to \waos/. Usually, the defaults are fine, but advanced users may want to change them.
+
+\startTABLE
+ \NC \plainop/
+ \NC\tex{widowpenalty=\meta{integer}}
+ \NC\NR\NC\NC\tex{clubpenalty=\meta{integer}}
+ \NC\NR
+ \NC \LaTeX{}
+ \NC\tex{lwcsetup\{widowpenalty = \meta{integer}\}}
+ \NC\NR\NC\NC\tex{lwcsetup\{orphanpenalty = \meta{integer}\}}
+ \NC\NR
+ \NC \ConTeXt{}
+ \NC\tex{setuplwc[widowpenalty = \meta{integer}]}
+ \NC\NR\NC\NC\tex{setuplwc[orphanpenalty = \meta{integer}]}
+ \NC\NR
+\stopTABLE
+
+Some suitable integers:
+
+\startTABLE
+ \NC Ignore widows/orphans \NC 0 \NC\NR
+ \NC Default \NC 1 \NC\NR
+ \NC Disable \lwc/ \NC 10\,000 \NC\NR
+\stopTABLE
+
+\subsection{\tex{nobreak} Behaviour}
+
+When \lwc/ encounters an orphan, it removes it by removing the orphaned line to the next page. However, sometimes, an orphan is immediately preceded by a section heading or a \tex{nobreak} command. By moving the orphan to the next page, you would naïvely separate a section (or other such material) from the line that follows. This really ought to be avoided, so \lwc/ provides some options to avoid this.
+
+\startTABLE
+ \NC \plainop/
+ \NC\tex{directlua\{lwc.nobreak_behaviour = "\meta{value}"\}}
+ \NC\NR
+ \NC \LaTeX{}
+ \NC\tex{lwcsetup\{nobreak = \meta{value}\}}
+ \NC\NR
+ \NC \ConTeXt{}
+ \NC\tex{setuplwc[nobreak = \meta{value}]}
+ \NC\NR
+\stopTABLE
+
+The default value, \type{keep}, \emph{keep}s the section heading with the orphan by moving both to the next page.
+
+The value \type{split} \emph{split}s up the section heading and the orphan by moving the orphan to the next page while leaving the heading behind. This is usually a bad idea.
-\Lwc/ automatically patches the default \LaTeX{}, \ConTeXt{}, and Plain~\TeX{} section commands, so you shouldn't need to patch these yourself; however, \lwc/ does \bold{not} patch the non-standard section commands provided by \sans{memoir}, \sans{\acronym{KOMA}-script}, \sans{titlesec}, and others. You'll need to patch these yourself.
+The value \type{warn} causes \lwc/ to give up on the page and do nothing, leaving an orphaned line. \Lwc/ \emph{warn}s the user so that they can manually remove the orphan.
-Under \ConTeXt{}, you need to use a different method to selectively disable \lwc/. Instead of using \tex{lwcdisablecmd}, you should use one of the preexisting \tex{everyfoo} hooks as shown in the table above since patching commands is rarely advisable in \ConTeXt{}
+\subsection{Maximum Cost}
-No commands are provided for selectively disabling \lwc/ with Op\TeX{}. Users must implement this functionality themselves.
+When \TeX{} breaks a paragraph, it scores it by the number of \q{demerits}. The demerits for a paragraph is the sum of the squared badnesses for each line, plus any \q{additional demerits} added for any other reason. The badness for a line is proportional the cube of the glue stretch ratio, so demerits grow with the sixth power of glue stretch.
+
+To choose the \q{best} paragraph on the page, \lwc/ uses a \q{cost function} $C$ that is initially defined as \startformula
+ C = \frac{d}{2\sqrt{l}}
+\stopformula where $d$ is the total demerits of the paragraph, and $l$ is the number of lines in the paragraph.
+
+By default, \lwc/ just selects the paragraph on the page with the lowest cost; however, you can configure it to only select paragraphs below a selected cost. If there aren't any paragraphs below the set threshold, then \lwc/ won't remove the \woo/ and will instead issue a warning.
+
+\startTABLE
+ \NC \plainop/
+ \NC\tex{lwcmaxcost=\meta{integer}}
+ \NC\NR
+ \NC \LaTeX{}
+ \NC\tex{lwcsetup\{max-cost = \meta{integer}\}}
+ \NC\NR
+ \NC \ConTeXt{}
+ \NC\tex{setuplwc[maxcost = \meta{integer}]}
+ \NC\NR
+\stopTABLE
+
+Very advanced users may also set a custom cost function by redefining the \inlineLUA{lwc.paragraph_cost(demerits, lines)} function.
+
+\subsection{Debug Mode}
+
+\Lwc/ offers a \q{debug} mode that prints extra information in the log files. This may be helpful to understand how \lwc/ is processing paragraphs and pages.
+
+\startTABLE
+ \NC \plainop/
+ \NC\inlineTEX{\directlua{lwc.debug = true}}
+ \NC\NR\NC\NC\inlineTEX{\directlua{lwc.debug = false}}
+ \NC\NR
+ \NC \LaTeX{}
+ \NC\inlineTEX{\lwcsetup{debug = true}}
+ \NC\NR\NC\NC\inlineTEX{\lwcsetup{debug = false}}
+ \NC\NR
+ \NC \ConTeXt{}
+ \NC\inlineTEX{\setuplwc[state = start]}
+ \NC\NR\NC\NC\inlineTEX{\setuplwc[state = stop]}
+ \NC\NR
+\stopTABLE
+
+\section{Columns}
+
+Since \TeX{} implements column breaking and page breaking through the same internal mechanisms, \lwc/ should remove \waos/ between columns just as well as it does with \waos/ between pages. This has been tested with the standard \LaTeX{} class option \type{twocolumn} and the two-column output routine from Chapter~23 of \cite[title][texbook].\cite[texbook] \Lwc/ should presumably work with any other multi-column implementation; however, due to the diversity and complexity of output~routines, this cannot be guaranteed.
\section[sec:issues]{Known Issues}
\startitemize
- \item \Lwc/ will not expand the first paragraph of a document.
-
\item \Lwc/ will rarely fail to correctly move the last line on an expanded page to the next page in documents with \emph{very} small paper sizes.
\item When a 3-line paragraph is at the end of a page forming a widow, \lwc/ will remove the widow; however, it will leave an orphan. This issue is inherent to any process that removes widows through paragraph expansion and is thus unavoidable. Orphans are better than widows, so this is still an improvement.
@@ -289,7 +427,9 @@ No commands are provided for selectively disabling \lwc/ with Op\TeX{}. Users mu
\item When running under \LuaMetaTeX, the log may be filled with lines like \q{\tt luatex warning > tex: left parfill skip is gone}. This is harmless and can be ignored. % I have no idea how to fix this. The LMTX manual has "\parfillleftskip" in the indices, but that's all that I can find about it.
- \item \Lwc/ will rarely raise a \q{\tt Circular node list detected!} warning. This occurs when the replacement paragraph node list loops back on itself. Since there is no \q{end} to the paragraph, \lwc/ cannot splice the paragraph into the page. The only reasonable option in this scenario is to stop processing the page, without removing the \woo/.
+ \item \Lwc/ will rarely raise a \q{\tt Circular node list detected!} warning. This occurs when the replacement paragraph node list loops back on itself. This is usually harmless, but can rarely cause the entire compile to completely fail.
+
+ \item \TeX{} may warn you about over or underfull vboxes on pages where \lwc/ removed a \woo/. This is a false alarm and can be ignored.
\stopitemize
\section{The Algorithm}
@@ -307,7 +447,7 @@ After \TeX{} has broken its paragraph into its natural length, \lwc/ appears onc
First, \lwc/ analyzes the \openalty/ of the page or column. If the page was broken at a \woo/, the \openalty/ will equal either \tex{widow\allowbreak penalty} or \tex{orphan\allowbreak penalty}. If the \openalty/ is not indicative of a \woo/, \lwc/ will stop and return \tex{box255} unmodified.
-At this point, we know that we have a \woo/ on the page, so we must lengthen the page by 1~line. We iterate through the list of saved paragraphs to find the lengthened paragraph with the least demerits. Once we've selected a paragraph to replace, we can now traverse through the page to find the original version of this paragraph that \TeX{} originally typeset. Once we find the original paragraph, we \q{splice} the lengthened paragraph in the place of the original.
+At this point, we know that we have a \woo/ on the page, so we must lengthen the page by 1~line. We iterate through the list of saved paragraphs to find the lengthened paragraph with the least cost. Once we've selected a paragraph to replace, we can now traverse through the page to find the original version of this paragraph that \TeX{} originally typeset. Once we find the original paragraph, we \q{splice} the lengthened paragraph in the place of the original.
Since the page is now 1~line longer than it was before, we pull the last line off of the page to bring it back to its original length. We place the line onto the top of the \emph{recent contributions} list so that it is added to the start of the next page. Now, we can return the new, widow-free page to the output routine.
@@ -353,14 +493,10 @@ Please note that a compiled document is \bold{not} considered to be an \q{Execut
\typeTEXfile{../source/lua-widow-control.sty}
\def\module{\tex{module}}
-\subsection{t-lua-widow-control.mkxl}
+\subsection{t-lua-widow-control.mkxl/mkiv}
\typeTEXfile{../source/t-lua-widow-control.mkxl}
-\subsection{t-lua-widow-control.mkiv}
-
-\typeTEXfile{../source/t-lua-widow-control.mkiv}
-
\subsection{lua-widow-control.opm}
\typeTEXfile{../source/lua-widow-control.opm}
diff --git a/macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkiv b/macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkiv
index 6eff5c9b00..e24f972d57 100644
--- a/macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkiv
+++ b/macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkiv
@@ -1,10 +1,10 @@
%D \module
%D [ file=t-lua-widow-control,
-%D version=1.1.6, %%version
+%D version=2.0.0, %%version
%D title=lua-widow-control,
%D subtitle=\ConTeXt module for lua-widow-control,
%D author=Max Chernoff,
-%D date=2022-02-22, %%date
+%D date=2022-03-07, %%dashdate
%D copyright=Max Chernoff,
%D license=MPL-2.0+,
%D url=https://github.com/gucci-on-fleek/lua-widow-control]
@@ -15,9 +15,9 @@
\installcommandhandler \????lwc {lwc} \????lwc
-\newdimen\lwcemergencystretch
+\newdimen\lwc_emergency_stretch
\appendtoks
- \lwcemergencystretch=\lwcparameter{emergencystretch}
+ \lwc_emergency_stretch=\lwcparameter{emergencystretch}
\to\everysetuplwc
\appendtoks
@@ -28,23 +28,49 @@
}
\to\everysetuplwc
+\appendtoks
+ \doifelse{\lwcparameter{debug}}\v!start{
+ \ctxlua{lwc.debug = true}
+ }{
+ \ctxlua{lwc.debug = false}
+ }
+\to\everysetuplwc
+
+\appendtoks
+ \ctxlua{lwc.nobreak_behaviour = "\lwcparameter{nobreak}"}
+\to\everysetuplwc
+
+\newcount\lwc_max_cost
+\appendtoks
+ \lwc_max_cost=\lwcparameter{maxcost}
+\to\everysetuplwc
+
+\appendtoks
+ % We can't just set the penalties because they will be reset automatically
+ % at \\starttext.
+ \startsetups[*default]
+ \clubpenalty=\lwcparameter{orphanpenalty}
+ \widowpenalty=\lwcparameter{widowpenalty}
+ \displaywidowpenalty=\lwcparameter{widowpenalty}
+ \stopsetups
+
+ \setups[*default]
+\to\everysetuplwc
+
\define\iflwc{\ctxlua{lwc.if_lwc_enabled()}}
\ctxloadluafile{lua-widow-control}
-\setuplwc[emergencystretch=3em, \c!state=\v!start]
-
-% We can't just set the penalties because they will be reset automatically
-% at \\starttext.
-\startsetups[*default]
- \clubpenalty=1
- \widowpenalty=1
- \displaywidowpenalty=0
- \interlinepenalty=0
- \brokenpenalty=0
-\stopsetups
+\setuplwc[
+ emergencystretch=3em,
+ \c!state=\v!start,
+ debug=\v!stop,
+ orphanpenalty=1,
+ widowpenalty=1,
+ nobreak=keep,
+ maxcost=2147483647,
+]
-\setups[*default]
% Here, we enable font expansion/contraction. It isn't strictly necessary for
% \lwc/'s functionality; however, it is required for the
@@ -55,23 +81,23 @@
% Expansion of some parts of the document, such as section headings, is quite
% undesirable, so we'll disable \lwc/ for certain commands.
% We should only reenable \lwc/ at the end if it was already enabled.
-\newif\iflwc@should@reenable
+\newif\iflwc_should_reenable
-\define\lwc@patch@pre{%
+\define\lwc_patch_pre{%
\iflwc%
- \lwc@should@reenabletrue%
+ \lwc_should_reenabletrue%
\setuplwc[state=stop]%
\else%
- \lwc@should@reenablefalse
+ \lwc_should_reenablefalse
\fi%
}
-\define\lwc@patch@post{\iflwc@should@reenable%
+\define\lwc_patch_post{\iflwc_should_reenable%
\setuplwc[state=start]%
\fi}
-\prependtoks\lwc@patch@pre\to\everybeforesectionheadhandle % Sectioning
-\prependtoks\lwc@patch@post\to\everyaftersectionheadhandle
+\prependtoks\lwc_patch_pre\to\everybeforesectionheadhandle % Sectioning
+\prependtoks\lwc_patch_post\to\everyaftersectionheadhandle
\protect
\stopmodule
diff --git a/macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkxl b/macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkxl
index 6eff5c9b00..e24f972d57 100644
--- a/macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkxl
+++ b/macros/luatex/generic/lua-widow-control/t-lua-widow-control.mkxl
@@ -1,10 +1,10 @@
%D \module
%D [ file=t-lua-widow-control,
-%D version=1.1.6, %%version
+%D version=2.0.0, %%version
%D title=lua-widow-control,
%D subtitle=\ConTeXt module for lua-widow-control,
%D author=Max Chernoff,
-%D date=2022-02-22, %%date
+%D date=2022-03-07, %%dashdate
%D copyright=Max Chernoff,
%D license=MPL-2.0+,
%D url=https://github.com/gucci-on-fleek/lua-widow-control]
@@ -15,9 +15,9 @@
\installcommandhandler \????lwc {lwc} \????lwc
-\newdimen\lwcemergencystretch
+\newdimen\lwc_emergency_stretch
\appendtoks
- \lwcemergencystretch=\lwcparameter{emergencystretch}
+ \lwc_emergency_stretch=\lwcparameter{emergencystretch}
\to\everysetuplwc
\appendtoks
@@ -28,23 +28,49 @@
}
\to\everysetuplwc
+\appendtoks
+ \doifelse{\lwcparameter{debug}}\v!start{
+ \ctxlua{lwc.debug = true}
+ }{
+ \ctxlua{lwc.debug = false}
+ }
+\to\everysetuplwc
+
+\appendtoks
+ \ctxlua{lwc.nobreak_behaviour = "\lwcparameter{nobreak}"}
+\to\everysetuplwc
+
+\newcount\lwc_max_cost
+\appendtoks
+ \lwc_max_cost=\lwcparameter{maxcost}
+\to\everysetuplwc
+
+\appendtoks
+ % We can't just set the penalties because they will be reset automatically
+ % at \\starttext.
+ \startsetups[*default]
+ \clubpenalty=\lwcparameter{orphanpenalty}
+ \widowpenalty=\lwcparameter{widowpenalty}
+ \displaywidowpenalty=\lwcparameter{widowpenalty}
+ \stopsetups
+
+ \setups[*default]
+\to\everysetuplwc
+
\define\iflwc{\ctxlua{lwc.if_lwc_enabled()}}
\ctxloadluafile{lua-widow-control}
-\setuplwc[emergencystretch=3em, \c!state=\v!start]
-
-% We can't just set the penalties because they will be reset automatically
-% at \\starttext.
-\startsetups[*default]
- \clubpenalty=1
- \widowpenalty=1
- \displaywidowpenalty=0
- \interlinepenalty=0
- \brokenpenalty=0
-\stopsetups
+\setuplwc[
+ emergencystretch=3em,
+ \c!state=\v!start,
+ debug=\v!stop,
+ orphanpenalty=1,
+ widowpenalty=1,
+ nobreak=keep,
+ maxcost=2147483647,
+]
-\setups[*default]
% Here, we enable font expansion/contraction. It isn't strictly necessary for
% \lwc/'s functionality; however, it is required for the
@@ -55,23 +81,23 @@
% Expansion of some parts of the document, such as section headings, is quite
% undesirable, so we'll disable \lwc/ for certain commands.
% We should only reenable \lwc/ at the end if it was already enabled.
-\newif\iflwc@should@reenable
+\newif\iflwc_should_reenable
-\define\lwc@patch@pre{%
+\define\lwc_patch_pre{%
\iflwc%
- \lwc@should@reenabletrue%
+ \lwc_should_reenabletrue%
\setuplwc[state=stop]%
\else%
- \lwc@should@reenablefalse
+ \lwc_should_reenablefalse
\fi%
}
-\define\lwc@patch@post{\iflwc@should@reenable%
+\define\lwc_patch_post{\iflwc_should_reenable%
\setuplwc[state=start]%
\fi}
-\prependtoks\lwc@patch@pre\to\everybeforesectionheadhandle % Sectioning
-\prependtoks\lwc@patch@post\to\everyaftersectionheadhandle
+\prependtoks\lwc_patch_pre\to\everybeforesectionheadhandle % Sectioning
+\prependtoks\lwc_patch_post\to\everyaftersectionheadhandle
\protect
\stopmodule