diff options
13 files changed, 1276 insertions, 1 deletions
diff --git a/Master/texmf-dist/doc/luatex/lua-widow-control/README.md b/Master/texmf-dist/doc/luatex/lua-widow-control/README.md new file mode 100644 index 00000000000..6f113569b46 --- /dev/null +++ b/Master/texmf-dist/doc/luatex/lua-widow-control/README.md @@ -0,0 +1,17 @@ +<!-- lua-widow-control + https://github.com/gucci-on-fleek/lua-widow-control + SPDX-License-Identifier: MPL-2.0+ OR CC-BY-SA-4.0+ + SPDX-FileCopyrightText: 2021 gucci-on-fleek +--> + +lua-widow-control +================= + +Lua-widow-control is a Plain TeX/LaTeX/ConTeXt package that removes widows and orphans without any user intervention. Using the power of LuaTeX, it does so _without_ stretching any glue or shortening any pages. Instead, lua-widow-control automatically lengthens a paragraph on a page where a widow or orphan would otherwise occur. + +Please see the [full documentation](https://github.com/gucci-on-fleek/lua-widow-control/releases/latest/download/lua-widow-control.pdf) for more. + +Licence +------- + +Lua-widow-control is licensed under the [_Mozilla Public License_, version 2.0](https://www.mozilla.org/en-US/MPL/2.0/) or greater. The documentation is additionally licensed under [CC-BY-SA, version 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode) or greater. diff --git a/Master/texmf-dist/doc/luatex/lua-widow-control/lua-widow-control.pdf b/Master/texmf-dist/doc/luatex/lua-widow-control/lua-widow-control.pdf Binary files differnew file mode 100644 index 00000000000..28acd457c65 --- /dev/null +++ b/Master/texmf-dist/doc/luatex/lua-widow-control/lua-widow-control.pdf diff --git a/Master/texmf-dist/source/luatex/lua-widow-control/lwc-documentation.bib b/Master/texmf-dist/source/luatex/lua-widow-control/lwc-documentation.bib new file mode 100644 index 00000000000..4778435f1e4 --- /dev/null +++ b/Master/texmf-dist/source/luatex/lua-widow-control/lwc-documentation.bib @@ -0,0 +1,57 @@ +% lua-widow-control +% https://github.com/gucci-on-fleek/lua-widow-control +% SPDX-License-Identifier: MPL-2.0+ OR CC-BY-SA-4.0+ +% SPDX-FileCopyrightText: 2021 gucci-on-fleek + +@book{texbook, + author = {Donald E. Knuth}, + title = {The \TeX{}Book}, + year = {2020}, + publisher = {Addison--Wesley}, + url = {https://ctan.org/pkg/texbook} +} + +@manual{widows-and-orphans, + author = {Frank Mittelbach}, + year = {2021}, + month = {March}, + title = {The \sans{widows-and-orphans} package}, + url = {https://www.ctan.org/pkg/widows-and-orphans} +} + +@article{tugboat-strategies, + author = {Paul Isambert}, + title = {Strategies against widows}, + journal = {TUGboat}, + number = {1}, + volume = {31}, + year = {2010}, + pages = {12--17}, + url = {https://www.tug.org/TUGboat/tb31-1/tb97isambert.pdf} +} + +@article{tugboat-widows, + author = {Frank Mittelbach}, + title = {Managing forlorn paragraph lines in \LaTeX}, + journal = {TUGboat}, + number = {3}, + volume = {39}, + year = {2018}, + pages = {246--251}, + url = {https://www.tug.org/TUGboat/tb39-3/tb123mitt-widows.pdf} +} + +@electronic{widow-assist, + author = {jeremie}, + title = {Paragraph callback to help with widows/orphans hand tuning}, + url = {https://tex.stackexchange.com/q/372062}, + year = {2017}, + month = {August}, +} + +@book{texbytopic, + author = {Victor Eijkhout}, + title = {\TeX by Topic}, + year = {2007}, + url = {https://texdoc.org/serve/texbytopic/0} +} diff --git a/Master/texmf-dist/source/luatex/lua-widow-control/lwc-documentation.mkxl b/Master/texmf-dist/source/luatex/lua-widow-control/lwc-documentation.mkxl new file mode 100644 index 00000000000..f8208bcc4cf --- /dev/null +++ b/Master/texmf-dist/source/luatex/lua-widow-control/lwc-documentation.mkxl @@ -0,0 +1,292 @@ +% lua-widow-control +% https://github.com/gucci-on-fleek/lua-widow-control +% SPDX-License-Identifier: MPL-2.0+ +% SPDX-FileCopyrightText: 2021 gucci-on-fleek + +\startenvironment[lwc-documentation] + +\mainlanguage[en] + +%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%% Font Selection %%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%% + +\startluacode +fonts.handlers.otf.addfeature { + name = "emdash_kern", -- Increase sidebearings on em-dash + type = "single", + data = { + ["—"] = { 100, 0, 200, 0 }, + } +} +\stopluacode + +\definefontfeature[default][default][ + protrusion=quality, + expansion=quality, + onum=yes, + script=latn, + emdash_kern=yes, +] +\setupalign[hz, hanging] + +\definefontfeature[lining][onum=no,lnum=yes] +\define\lining{\feature[+][lining]} + +\starttypescript[lwc-fonts] + \definetypeface[lwc-fonts] [rm] [serif][pagella] [default] + \definetypeface[lwc-fonts] [ss] [sans] [libertinus] [default] [rscale=1.07] + \definetypeface[lwc-fonts] [tt] [mono] [plex] [default] [rscale=0.89] + \definetypeface[lwc-fonts] [mm] [math] [pagella] [default] +\stoptypescript + +\setupbodyfont[lwc-fonts, 11pt] + +\setupbodyfontenvironment[default][em=italic] + + +%%%%%%%%%%%%%%%%%%%%%%% +%%%%% Page Layout %%%%% +%%%%%%%%%%%%%%%%%%%%%%% + +\setuppapersize[letter] + +\setupindenting[yes, 3em] +\setupinterlinespace[2.75ex] + +\input lang-frq.mkxl % For \averagecharwidth + +\setuplayout[ + width=75\averagecharwidth, + backspace=\dimexpr(\paperwidth - \makeupwidth) / 2, + topspace=\dimexpr\backspace - \headerheight, + footerdistance=3\baselineskip, + footer=\baselineskip, + height=8.75in, + margin=\dimexpr\backspace - \margindistance - 0.25cm, +] + +%%%%%%%%%%%%%%%%%%%%%%%% +%%%%% PDF Settings %%%%% +%%%%%%%%%%%%%%%%%%%%%%%% + +% PDF/UA +\setupbackend[format=PDF/UA-1] +\setupinteraction[state=start, focus=standard] +\setuptagging[state=start] +\setupstructure[state=start, method=auto] + +% Bookmarks +\placebookmarks[section, subsection, filename][section, subsection, filename][number=no] +\setupinteractionscreen[option=bookmark] + +%%%%%%%%%%%%%%%%%%%%%% +%%%%% Formatting %%%%% +%%%%%%%%%%%%%%%%%%%%%% + +\setuppagenumbering[location=footer, style=\ss\lining] + +% Acronym styling +\definecharacterkerning[acronymkerning][factor=0.05] +\definealternativestyle[acronymstyle][{\word\sc\switchtobodyfont[1.1em]\setcharacterkerning[acronymkerning]}][] +\definehighlight[acronym][style=acronymstyle] + +\startuniqueMPgraphic{warning} +path p; +p := roundedsquare(OverlayWidth, OverlayHeight, 0.25cm); +fill p withcolor black; +draw p anglestriped (1, 45, 20) withpen pencircle scaled 10pt withcolor yellow; +fill p blownup -0.125cm withcolor white; +draw p blownup -0.125cm; +setbounds currentpicture to boundingbox OverlayBox; +\stopuniqueMPgraphic +\defineoverlay[warning][\useMPgraphic{warning}] + +\define[1]\warning{ + \blank[big] + \midaligned{\framed[ + frame=off, + background=warning, + backgroundoffset=0.25cm, + width=\dimexpr\hsize-4em, + align=flushleft, + ]{% + {\ssa\bf Warning} + + #1 + }} + \blank[big] +} + +\setupitemize[each][packed][style=\lining] +\setupcaptions[headstyle=\ssbf\lining, style=\ss] + +\setupdelimitedtext[blockquote][ + style=\ss, + before=\noindentation, + after={\blank[medium]} +] + +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%% Section Commands %%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\definecharacterkerning[titlekern][factor=0.2] + +% Section +\startsetups[style:section] + \setcharacterkerning[titlekern] + \switchtobodyfont[adventor] + \word + \bfb + \feature[+][smallcaps] + \spaceskip=0.4em + \veryraggedcenter +\stopsetups + +\setuphead[section][ + before={\setuplwc[state=stop]\blank[big, preference]}, + after={\blank[medium, samepage]\setuplwc[state=start]}, + textstyle=\setups{style:section}, + number=no, +] + +% Subsection +\setuphead[subsection][ + before={\blank[medium, preference]}, + after=, + textstyle=\ss, + alternative=margintext, + number=no, +] + +\definehead[filename][subsection][ + alternative=normal, + style=\ssita, + before={\blank[big, preference]}, +] + +% TOC +\define[3]\TOCcommand{ + \midaligned{ + \llap{#2} + \hskip 1em + \rlap{\color[\interactionparameter{color}]{\bf #3}} + } +} + +\setupcombinedlist[content][list=section] +\setuplist[section][ + alternative=interactive, + command=\TOCcommand, + after=\par +] + +%%%%%%%%%%%%%%%%%%%%%%% +%%%%% Title Block %%%%% +%%%%%%%%%%%%%%%%%%%%%%% + +\setupdocument[ + title={}, + author={}, + version={}, +] + +\startsetups[titleblock] + \startalignment [middle] + \begingroup + \setups[style:section]\bfc + \documentvariable{title} + \par + \endgroup + + \blank[big] + + \begingroup + \ssa\lining + v\,\documentvariable{version} + \par + \endgroup + + \dontleavehmode + \blackrule[depth=-0.25\baselineskip, height=\dimexpr0.25\baselineskip + 0.4pt, width=8em] + \par + + \from[projecturl] + \blank[big] + + \noindentation + \stopalignment +\stopsetups + +\startsetups[document:start] + \setup[titleblock] + + \setupinteraction[ + title=\documentvariable{title}, + author=\documentvariable{author}, + ] +\stopsetups + +%%%%%%%%%%%%%%%%%%%%%%%% +%%%%% Bibliography %%%%% +%%%%%%%%%%%%%%%%%%%%%%%% +\usebtxdefinitions[apa] + +\setupbtx[apa:list][ + stopper:initials=, + separator:initials=\btxnbsp, + interaction=start, +] + +\setupbtxlabeltext[en][ + apa:Retrieved={} +] + +\setupbtxrendering[apa][ + numbering=yes, + sorttype=used, +] + +\setupbtx[apa:cite][alternative=num] + +\defineshift[citeshift][ + method=0, + dy=-1, + unit=ex, + continue=yes, + style=\tfx\lining, +] + +\setupbtx[apa:cite:num][ + command=\citeshift, + left=, + right=, + separator:2=\citeshift{,\,}, + separator:3=\citeshift{--} +] + +\setupbtxlist[apa][ + alternative=a, + before=, + after=, + style=\lining, + margin=0pt, + width=1.5em, +] + +\unprotect +\starttexdefinition mutable protected btx:apa:url + \goto{ + \hyphenatedurl{ + \clf_btxflush{\currentbtxdataset}{\currentbtxtag}{url} + } + }[ + url( + \clf_btxflush{\currentbtxdataset}{\currentbtxtag}{url} + ) + ] +\stoptexdefinition +\protect + +\stopenvironment diff --git a/Master/texmf-dist/source/luatex/lua-widow-control/lwc-documentation.tex b/Master/texmf-dist/source/luatex/lua-widow-control/lwc-documentation.tex new file mode 100644 index 00000000000..adc8e119aa3 --- /dev/null +++ b/Master/texmf-dist/source/luatex/lua-widow-control/lwc-documentation.tex @@ -0,0 +1,309 @@ +%!TeX program = context + +% lua-widow-control +% https://github.com/gucci-on-fleek/lua-widow-control +% SPDX-License-Identifier: MPL-2.0+ OR CC-BY-SA-4.0+ +% SPDX-FileCopyrightText: 2021 gucci-on-fleek + +% Warning: Compiles slowly + +\environment lwc-documentation + +\usemodule[lua-widow-control] +\usebtxdataset[lwc-documentation.bib] + +\useURL[projecturl][https://github.com/gucci-on-fleek/lua-widow-control] +\useURL[download][https://github.com/gucci-on-fleek/lua-widow-control/releases/latest][][latest release] + +% Abbreviations +\def\lwc/{\sans{lua-\allowbreak widow-\allowbreak control}} +\def\Lwc/{\sans{Lua-\allowbreak widow-\allowbreak control}} +\def\estretch/{\tex{emergency\allowbreak stretch}} +\def\openalty/{\tex{output\allowbreak penalty}} +\def\waos/{widows and orphans} +\def\wao/{widow and orphan} +\def\woo/{widow or orphan} +\let\q=\quotation + +\def\titlecite[#1]{\cite[title][#1]\cite[#1]} + +\usemodule[vim] +\definevimtyping[TEX][syntax=tex, lines=split, escape=comment] +\definevimtyping[LUA][syntax=lua, lines=split, escape=comment] + +\input lwc-sample + +\startdocument[ + title=lua-widow-control, + author=gucci-on-fleek, + version=1.0.0, %%version +] + +\Lwc/ is a Plain~\TeX/\LaTeX/\ConTeXt{} 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. Instead, \lwc/ automatically lengthens a paragraph on a page where a \woo/ would otherwise occur. + +\section{Quick Start} +Install \lwc/ by unzipping the \from[download] in your \type{TEXMFLOCAL} directory. Rebuild the filename database. Place \inlineTEX{\usepackage{lua-widow-control}} in the preamble of your document. + +For a more detailed explanation of the steps involved and a description of some of the finer points involved with using \lwc/, keep reading. + +\subject{Contents} +\placecontent[criterium=all] + +\warning{\Lwc/ works by modifying the internal structure of each page. In \bold{rare} scenarios, this could delete or duplicate paragraphs or lines when a page break is triggered. All known bugs have been fixed, but I would still caution against using \lwc/ in any document where a single missing line would be disastrous. \Lwc/ should be sufficiently stable to use in typical documents.} + +\section{Motivation} +\TeX{} provides top-notch typesetting: even 40 years after its first release, no other program produces higher quality mathematical typesetting, and its paragraph-breaking algorithm is still state-of-the-art. However, its page breaking is not quite as sophisticated as its paragraph breaking and thus suffers from some minor issues. + +Unmodified \TeX{} typically has only 2 ways of dealing with \waos/: it can either shorten a page by 1 line, or it can stretch out some vertical whitespace. \TeX{} was designed for mathematical and scientific typesetting, where a typical page has multiple section headings, tables, figures, and equations. For this style of document, \TeX's default behavior works quite well; however, for prose or any other document composed almost entirely of text, there is no vertical whitespace to stretch. + +Since there were no ready-made and fully-automated solutions to remove \waos/ from prosaic documents, I decided to create \lwc/. + +\section{Widows and Orphans} +\subsection{Widows} +Widows occur when when the majority of a paragraph is on one page, but the last line is on the following page. Widows are undesirable for both aesthetics and readability. Aesthetically, it looks quite odd for a lone line to be at the start of the page. Functionally, the separation of a paragraph and its last line disconnects the two, causing the reader to lose context for the widowed line. + +\subsection{Orphans} +Orphans are when the first line of a paragraph occurs on the page before the remainder of they paragraph. They are not nearly as distracting for the reader, but they are still not ideal. Visually, \waos/ are about equally disruptive; however, orphans tend not to decrease the legibility of a text as much as widows do, so they tend to be ignored more often. + +\startplacefigure[location={here, top, bottom}, title={A visual comparison of \waos/.}, reference=tab:widow] + \getbuffer[widow-orphan] +\stopplacefigure + +\section{\TeX's Pagination} +\subsection{Algorithm} +It is tricky to understand how \lwc/ works if you aren't familiar with how \TeX{} breaks pages. Chapter~15 of \titlecite[texbook] (\q{How \TeX{} Makes +Lines into Pages}) is the best reference for this; however, it goes into much more detail than most users require. As a supplemental resource, I can also recommend Section~27 of \titlecite[texbytopic], \goto{available online}[url(https://texdoc.org/serve/texbytopic/0#page=227)] for free. Below follows a \emph{very} simplified (and likely error-ridden) summary of \TeX{}'s page breaking algorithm: + +\TeX{} fills the page with lines and other objects until the next object will no longer fit. Once no more objects will fit, \TeX{}, will align the bottom of the last line with the bottom of the page by stretching any vertical spaces. + +However, some objects have penalties attached. These penalties make \TeX{} treat the object as if it is longer or shorter for the sake of page breaking. By default, \TeX{} assigns a penalties to the first and last lines of a paragraph (\waos/). This makes \TeX{} treat them as if they are larger or smaller than their actual size such that \TeX{} tends not to break them up. + +One important note: once \TeX{} begins breaking a page, it only breaking a page. That is, it will never go back and modify any content that has previously been added to its \emph{main vertical list}. That is, page breaking is a localized algorithm, without any backtracking. + +\subsection{Behavior} +Of course, this algorithm doesn't allow us to intuitively understand how \TeX{} deals with \waos/. + +Due to the penalties attached to \waos/, \TeX{} treats them as if they are longer than they actually are. Widows and orphans with small penalties attached---like \LaTeX's default values of 150---are only treated as slightly taller than 1 line, while \waos/ with large penalties---values near 10\,000---are treated as if they are 2 lines tall. Because potential \wao/ lines are broken as if they are taller than they actually are, \TeX{} will tend to group them together on the same pages. + +However, when these lines are moved as a group, \TeX{} will have to make a page with less lines. \about[sec:demo] goes into further detail about how \TeX{} deals with these too-short pages. The main takeaway is that for a page exclusively filled with text, all of \TeX's builtin solutions come with compromises. + +\section{Previous Work} +There have been a few previous attempts to improve upon \TeX's previously-discussed \wao/-handling abilities; however, none of these have been able to automatically remove \waos/ without stretching any glue or shortening any pages. + +\titlecite[tugboat-strategies] and \titlecite[tugboat-widows] both begin with comprehensive discussions of the methods of preventing \waos/. They both agree that \waos/ are bad and ought to be avoided; however, they each differ in solutions. \italic{Strategies}\cite[tugboat-strategies] proposes an output routine that reduces the length of facing pages by 1 line if necessary to remove \waos/ while \italic{Managing}\cite[tugboat-widows] proposes that the author manually rewrites or adjusts the \tex{looseness} when needed. + +\titlecite[widow-assist] contains a file \type{widow-assist.lua} that automatically detects which paragraphs can be safely shortened or lengthened by 1 line. \titlecite[widows-and-orphans] alerts the author to the pages that contain widows or orphans. Combined, these packages make it very simple for the author to quickly remove \waos/ by adjusting the \tex{looseness} values; however, it still requires the author to make manual source changes after each revision. + +\Lwc/ is essentially just a combination of \type{widow-assist.lua}\cite[widow-assist] and \sans{widows-and-orphans}:\cite[widows-and-orphans] when the \openalty/ shows that a \woo/ occurred, Lua is used to find a stretchable paragraph. What \lwc/ adds on top of these packages is automation: \lwc/ eliminates the requirement for any manual adjustments. + +\startTEXpage[ + align=normal, + width=100cm, + autowidth=force, + offset=5pt, + pagestate=start +] + \veryraggedcenter + \setupTABLE[row][first][style=\ssbf, align=middle] + \setupTABLE[frame=off, offset=5pt] + \startTABLE + \NC Ignore + \NC Shorten + \NC Stretch + \NC \Lwc/ \NC\NR + + \NC \typesetbuffer[ignore][frame=on,page=1] + \NC \typesetbuffer[shorten][frame=on,page=1] + \NC \typesetbuffer[stretch][frame=on,page=1] + \NC \typesetbuffer[lwc][frame=on,page=1] + \NC\NR + + \NC \typesetbuffer[ignore][frame=on,page=2] + \NC \typesetbuffer[shorten][frame=on,page=2] + \NC \typesetbuffer[stretch][frame=on,page=2] + \NC \typesetbuffer[lwc][frame=on,page=2] + \NC\NR + + \NC \processTEXbuffer[ignore-code] + \NC \processTEXbuffer[shorten-code] + \NC \processTEXbuffer[stretch-code] + \NC \processTEXbuffer[lwc-code] + \NC\NR + \stopTABLE + + \placefloatcaption[table][reference=tab:demo, title={A visual comparison of various automated widow handling techniques.}] +\stopTEXpage + +\section[sec:demo]{Demonstration} + +Although \TeX{}'s page breaking algorithm is quite simple, it can lead to some fairly complex behaviors when \waos/ are involved. The usual choices are to either ignore them, stretch some glue, or shorten the page. \in{Table}[tab:demo] has a visual demonstration of some of these behaviors and how \lwc/ differs from the defaults. + +\subsection{Ignore} +As you can see, the last line of the page is on a separate page from the rest of its paragraph, creating a widow. This is usually pretty distracting for the reader, so it is best avoided wherever possible. + +\subsection{Shorten} +This page did not leave any widows, but it did shorten the previous page by 1~line. Sometimes this is acceptable, but usually it looks bad because each page will have different text-block heights. This can make the pages look quite uneven, especially when typesetting with columns or in a book with facing pages. + +\subsection{Stretch} +This page also has no widows and it has a flushed bottom margin. However, the space between each paragraph had to be stretched. + +If this page had many equations, headings, and other elements with natural space between them, the stretched out space would be much less noticeable. \TeX{} was designed for mathematical typesetting, so it makes sense that this is its default behavior. However, in a page with mostly text, these paragraph gaps can look unsightly. + +In addition, this method is incompatible with typesetting on a grid since all glue stretch is quantized to the height of a line. + +\subsection{\lwc/} +\Lwc/ has none of these issues: it eliminates the widows in a document while keeping a flushed bottom margin and constant paragraph spacing. + +To do so, \lwc/ lengthened the second paragraph by one line. If you look closely, you can see that this stretched the interword spaces. This stretching is noticeable when typesetting in a narrow text block, but it becomes nearly imperceptible with larger widths. + +\Lwc/ automatically finds the \q{best} paragraph to stretch, so the increase in interword spaces should almost always be minimal. + +\section{Installation} + +\subsection{\TeX~Live} +Run \type{tlmgr install lua-widow-control} in a terminal, or install using the \q{\TeX~Live Manager} \acronym{GUI}. + +\subsection{Mik\TeX} +Mik\TeX{} should automatically install the package when you include it in your document. + +\subsection{Manual} +Currently, \ConTeXt{} \acronym{MKXL} (\LuaMetaTeX{}) users must manually install the package. Most other users will be better served by using the \lwc/ supplied by \TeX{}Live and Mik\TeX{}; however, all users may manually install the package if desired. The procedure should be fairly similar regardless of your \acronym{OS}, \TeX{} distribution, or format. + +\subsection{Steps} +\startitemize[N] + \item Download \type{lua-widow-control.tds.zip} from the \from[download] on GitHub. + \item Unzip the release into your \type{TEXMFLOCAL/} directory. (You can find its location by running \type{kpsewhich --var-value TEXMFHOME} in a terminal) + \item Refresh the filename database: \startitemize[1] + \item \ConTeXt: \type{mtxrun --generate} + \item \TeX{}Live: \type{mktexlsr} + \item Mik\TeX: \type{initexmf --update-fndb} + \stopitemize +\stopitemize + +\section{Loading the Package} + +\subsection{Plain \TeX} + +\inlineTEX{\input lua-widow-control} + +\subsection{\LaTeX} + +\inlineTEX{\usepackage{lua-widow-control}} + +\subsection{\ConTeXt} + +\inlineTEX{\usemodule[lua-widow-control]} + +\section{Advanced Usage} + +\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. + +\setupTABLE[frame=off] + +\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 +\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 +\stopTABLE + +\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 +\stopTABLE + +\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. + +\section{Known Issues} +\startitemize + \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. This tends to only occur with paper narrower than $\approx\unit{8 cm}$ or shorter than $\approx\unit{10 cm}$ and should not affect any typical documents. + + \item In multicolumn layouts, \lwc/ will remove \waos/ that occur between pages, but it will not remove \waos/ between columns. To do so would likely require modifying the output routine which would be a very difficult task. + + \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. + + \item Sometimes a \woo/ just cannot be eliminated because no paragraph has enough \q{stretch}. This can \emph{sometimes} be remediated by increasing \lwc/'s \estretch/; however, some pages just don't have enough \q{stretchy} paragraphs. Long paragraphs with short words tend to be \q{stretchier} than short paragraphs with long words since these long paragraphs will have more interword glue. Narrow columns also stretch easier than wide columns since you need to expand a paragraph by less to make a new line. + + \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 not expand the first paragraph of a document. This should never be an issue in any real-world documents. +\stopitemize + +\section{The Algorithm} +\Lwc/ uses a fairly simple algorithm to eliminate \waos/. It is pretty basic, but there are a few subtleties. Please see \about[sec:implementation] for a full listing of the source code. + +\subsection{Paragraph Breaking} +First, \lwc/ hooks into the paragraph breaking process. + +Before a paragraph is broken by \TeX{}, \lwc/ grabs the unbroken paragraph. \Lwc/ then breaks the paragraph 1~line longer than its natural length and stores it for later. + +\Lwc/ does not interfere with how \TeX{} breaks paragraphs into their natural length. + +After \TeX{} has broken its paragraph into its natural length, \lwc/ appears once again. Before the broken paragraph is added to the main vertical list, \lwc/ tags the first and last nodes of the paragraph. These tags create a relationship between the previously-saved lengthened paragraph and the start/end of the naturally-typeset paragraph on the page. + +\subsection{Page Breaking} +\Lwc/ intercepts \tex{box255} after the output routine and before it is shipped out. + +First, \lwc/ analyzes the \openalty/ of the page. If the page was broken at a \woo/, the \openalty/ will have been set to the respective values of \tex{widow\allowbreak penalty} and \tex{orphan\allowbreak penalty}. If the \openalty/ is not indicative of a \woo/, \lwc/ will abort and return \tex{box255} unmodified. + +At this point, we know that we have a \woo/ on the page, so we know that we need to lengthen the page by 1~line. We can 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 so that we can splice in the lengthened paragraph. + +Finally, we pull the last line off of the page and then we place it onto the top of the \emph{recent contributions} list. Now, we can ship out the new, widow-free page. + +\section{Contributions} + +If you have any issues with \lwc/, please create an issue at the \goto{project's GitHub page}[url(projecturl)]. Or, if you are willing to help with any improvements, \goto{submit a \acronym{PR}}[url(projecturl)]. + +\section{License} + +\Lwc/ is licensed under the \goto{\emph{Mozilla Public License}, version 2.0}[url(https://www.mozilla.org/en-US/MPL/2.0/)] or greater. The documentation is licensed under \goto{\acronym{CC-BY-SA}, version 4.0}[url(https://creativecommons.org/licenses/by-sa/4.0/legalcode)] or greater as well as the \acronym{MPL}. + +\section{References} + +\placelistofpublications + +\page +\setuplayout[ + width=middle, + backspace=1in, + height=9.25in, +] +\section[sec:implementation]{Implementation} + +\setupbodyfont[10pt] + +\filename{lua-widow-control.lua} + +\typeLUAfile{../source/lua-widow-control.lua} + +\filename{lua-widow-control.tex} + +\typeTEXfile{../source/lua-widow-control.tex} + +\filename{lua-widow-control.sty} + +\typeTEXfile{../source/lua-widow-control.sty} + +\def\module{\tex{module}} +\filename{t-lua-widow-control.mkxl} + +\typeTEXfile{../source/t-lua-widow-control.mkxl} + +\filename{Demo from \in{Table}[tab:demo]} + +\typeTEXfile{lwc-documentation-demo-text.tmp} + +\stopdocument diff --git a/Master/texmf-dist/source/luatex/lua-widow-control/lwc-sample.tex b/Master/texmf-dist/source/luatex/lua-widow-control/lwc-sample.tex new file mode 100644 index 00000000000..eb844301cd5 --- /dev/null +++ b/Master/texmf-dist/source/luatex/lua-widow-control/lwc-sample.tex @@ -0,0 +1,116 @@ +% lua-widow-control +% https://github.com/gucci-on-fleek/lua-widow-control +% SPDX-License-Identifier: MPL-2.0+ OR CC-BY-SA-4.0+ +% SPDX-FileCopyrightText: 2021 gucci-on-fleek + +\startbuffer[demo-text] + \definepapersize[smallpaper][ + width=6cm, + height=9cm + ]\setuppapersize[smallpaper] + + \setuplayout[ + topspace=0.1cm, + backspace=0.1cm, + width=middle, + height=middle, + header=0pt, + footer=0pt, + ] + + \def\lwc/{\sans{lua-\allowbreak widow-\allowbreak control}} + \def\Lwc/{\sans{Lua-\allowbreak widow-\allowbreak control}} + + \setupbodyfont[9pt] + \setupindenting[yes, 2em] + \setupalign[tolerant] + + \definepalet[layout][grid=middlegray] + \showgrid[nonumber, none, lines] + + \definefontfeature[default][default][expansion=quality] + \setupalign[hz] + + \starttext + \Lwc/ can remove most widows and orphans from a document, \emph{without} stretching any glue or shortening any pages. + + It does so by automatically lengthening a paragraph on a page where a widow or orphan would otherwise occur. While \TeX{} breaks paragraphs into their natural length, \lwc/ is breaking the paragraph 1~line longer than its natural length. \TeX{}'s paragraph is output to the page, but \lwc/'s paragraph is just stored for later. When a widow or orphan occurs, \lwc/ can take over. It selects the previously-saved paragraph with the least badness; then, it replaces \TeX{}'s paragraph with its saved paragraph. This lengthens the page by 1~line. + + Now, the last line of the current page can be pushed to the top of the next page. This removes the widow or the orphan without creating any additional work for the document's author. + \stoptext +\stopbuffer +\savebuffer[list=demo-text] + +\startbuffer[shorten] + \parskip=0pt + \input lwc-documentation-demo-text.tmp +\stopbuffer + +\startbuffer[shorten-code] + \parskip=0pt + + \clubpenalty=10000 + \widowpenalty=10000 +\stopbuffer + +\startbuffer[stretch] + \parskip=0pt plus 1fill + \input lwc-documentation-demo-text.tmp +\stopbuffer + +\startbuffer[stretch-code] + \parskip=0pt plus 1fill + + \clubpenalty=10000 + \widowpenalty=10000 +\stopbuffer + +\startbuffer[ignore] + \startsetups[*default] + \clubpenalty=0 + \widowpenalty=0 + \displaywidowpenalty=0 + \interlinepenalty=0 + \brokenpenalty=0 + \stopsetups + + \setups[*default] + + \input lwc-documentation-demo-text.tmp +\stopbuffer + +\startbuffer[ignore-code] + \parskip=0pt + + \clubpenalty=0 + \widowpenalty=0 +\stopbuffer + +\startbuffer[lwc] + \usemodule[lua-widow-control] + \input lwc-documentation-demo-text.tmp +\stopbuffer + +\startbuffer[lwc-code] + \usepackage{lua-widow-control} +\stopbuffer + +\startbuffer[widow-orphan] + % TODO This is all really quite hacky + \define[1]\rulewords{\dorecurse{#1}{\blackrule[height=1.5ex, width=1em] \blackrule[height=1.5ex, width=2em] \blackrule[height=1.5ex, width=1.5em] \blackrule[height=1.5ex, width=3em] }} + + \define[2]\fakestart{\framed[width=broad, align=normal, frame=off]{\parfillskip=0pt\spaceskip=0.2em plus 1fill\hskip 5em\rulewords{#1}\blackrule[height=1.5ex, width=#2]}} + + \define[2]\fakeend{\framed[width=broad, align=normal, frame=off]{\parfillskip=5em\spaceskip=0.2em plus 1fill\rulewords{#1}\blackrule[height=1.5ex, width=#2]}} + + \setupTABLE[width=broad, frame=off] + \setupTABLE[row][1][align=middle, style=\ssbf] + \setupTABLE[row][2][align=low, frame=on] + \setupTABLE[row][3][align=high, frame=on] + \setupTABLE[column][2][frame=off, width=1em] + \startTABLE + \NC Widow \NC\NC Orphan \NC\NR + \NC\fakestart{5}{1.5em}\NC\NC\fakestart{1}{2em}\NC\NR + \NC\fakeend{1}{2em}\NC\NC\fakeend{5}{1.5em}\NC\NR + \stopTABLE +\stopbuffer diff --git a/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkxl b/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkxl new file mode 100644 index 00000000000..66eeadec579 --- /dev/null +++ b/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkxl @@ -0,0 +1,54 @@ +%D \module +%D [ file=t-lua-widow-control, +%D version=1.0.0, %%version +%D title=lua-widow-control, +%D subtitle=\ConTeXt module for lua-widow-control, +%D author=gucci-on-fleek, +%D date=2021-10-09, %%date +%D copyright=gucci-on-fleek, +%D license=MPL-2.0+, +%D url=https://github.com/gucci-on-fleek/lua-widow-control] +\startmodule[lua-widow-control] +\unprotect + +\installnamespace{lwc} + +\installcommandhandler \????lwc {lwc} \????lwc + +\newdimen\lwcemergencystretch +\appendtoks + \lwcemergencystretch=\lwcparameter{emergencystretch} +\to\everysetuplwc + +\appendtoks + \doifelse{\lwcparameter{\c!state}}\v!start{ + \ctxlua{lwc.enable_callbacks()} + }{ + \ctxlua{lwc.disable_callbacks()} + } +\to\everysetuplwc + +\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 + +\setups[*default] + +% 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. +\definefontfeature[default][default][expansion=quality] +\setupalign[hz] + +\protect +\stopmodule diff --git a/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control.sty b/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control.sty new file mode 100644 index 00000000000..d5ef8194f1b --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control.sty @@ -0,0 +1,33 @@ +% lua-widow-control +% https://github.com/gucci-on-fleek/lua-widow-control +% SPDX-License-Identifier: MPL-2.0+ +% SPDX-FileCopyrightText: 2021 gucci-on-fleek + +\NeedsTeXFormat{LaTeX2e}[2015/01/01] % Formats built after 2015 include \LuaTeX{}Base +\ProvidesPackage{lua-widow-control}% + [2021/10/09 v1.0.0] %%version %%date + +\setlength{\clubpenalty}{1} +\setlength{\widowpenalty}{1} +\setlength{\displaywidowpenalty}{0} +\setlength{\interlinepenalty}{0} +\setlength{\brokenpenalty}{0} + +% We can't use \\newlength since that makes a \TeX{} "skip", not a "dimen" +\newdimen\lwcemergencystretch +\setlength{\lwcemergencystretch}{3em} + +\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[final]{microtype} + +\newcommand{\lwcenable}{\directlua{lwc.enable_callbacks()}} +\newcommand{\lwcdisable}{\directlua{lwc.disable_callbacks()}} + +% Enable \lwc/ by default when the package is loaded. +\lwcenable + +\endinput diff --git a/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.lua b/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.lua new file mode 100644 index 00000000000..a85793d3a9e --- /dev/null +++ b/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.lua @@ -0,0 +1,363 @@ +--[[ + lua-widow-control + https://github.com/gucci-on-fleek/lua-widow-control + SPDX-License-Identifier: MPL-2.0+ + SPDX-FileCopyrightText: 2021 gucci-on-fleek + ]] + +lwc = {} +lwc.name = "lua-widow-control" + +--[[ + \lwc/ is intended to be format-agonistic. It only runs on Lua\TeX{}, + but there are still some slight differences between formats. Here, we + detect the format name then set some flags for later processing. + ]] +local format = tex.formatname + +if format:find('cont') then -- cont-en, cont-fr, cont-nl, ... + lwc.context = true +elseif format:find('latex') then -- lualatex, lualatex-dev, ... + lwc.latex = true +elseif format == 'luatex' then -- Plain + lwc.plain = true +end + +--[[ + Save some local copies of the node library to reduce table lookups. + This is probably a useless micro-optimization, but it can't hurt. + ]] +local last = node.slide +local copy = node.copy_list +local par_id = node.id("par") +local glue_id = node.id("glue") +local set_attribute = node.set_attribute +local has_attribute = node.has_attribute +local flush_list = node.flush_list or node.flushlist +local min_col_width = tex.sp("25em") +local maxdimen = 1073741823 -- \\maxdimen in sp + +--[[ + This error is raised in the following circumstances: + - When the user manually loads the Lua module without loading Lua\TeX{}Base + - When the package is used with an unsupported format + Both of these are pretty unlikely, but it can't hurt to check. + ]] +assert(lwc.context or luatexbase, [[ + + This module requires a supported callback library. Please + follow the following format-dependant instructions: + - LaTeX: Use a version built after 2015-01-01, or include + `\usepackage{luatexbase}' before loading this module. + - Plain: Include `\input ltluatex' before loading this module. + - ConTeXt: Use the LMTX version. +]]) + +--[[ + Package/module initialization + ]] +if lwc.context then + lwc.warning = logs.reporter("module", lwc.name) + lwc.attribute = attributes.public(lwc.name) + lwc.contrib_head = 'contribute_head' -- For \LuaMetaTeX{} +elseif lwc.plain or lwc.latex then + luatexbase.provides_module { + name = lwc.name, + date = "2021/10/09", --%%date + version = "1.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. + ]], + } + lwc.warning = function(str) luatexbase.module_warning(lwc.name, str) end + lwc.attribute = luatexbase.new_attribute(lwc.name) + lwc.contrib_head = 'contrib_head' -- For \LuaTeX{} +else -- uh oh + error("Unsupported format. Please use (Lua)LaTeX, Plain (Lua)TeX, or ConTeXt (MKXL/LMTX)") +end + +lwc.paragraphs = {} -- List to hold the alternate paragraph versions + +if tex.interlinepenalty ~= 0 then + lwc.warning [[ +\interlinepenalty is set to a non-zero value. +This may prevent lua-widow-control from +properly functioning. +]] +end + + +--[[ + Function definitions + ]] + +--- Create a table of functions to enable or disable a given callback +--- @param t table Parameters of the callback to create +--- callback: str = The \LuaTeX{} callback name +--- func: function = The function to call +--- name: str = The name/ID of the callback +--- category: str = The category for a \ConTeXt{} "Action" +--- position: str = The "position" for a \ConTeXt{} "Action" +--- lowlevel: bool = If we should use a lowlevel \LuaTeX{} callback instead of a +--- \ConTeXt{} "Action" +--- @return table t Enablers/Disablers for the callback +--- enable: function = Enable the callback +--- disable: function = Disable the callback +function lwc.register_callback(t) + if lwc.plain or lwc.latex then -- Both use \LuaTeX{}Base for callbacks + return { + enable = function() + luatexbase.add_to_callback(t.callback, t.func, t.name) + end, + disable = function() + luatexbase.remove_from_callback(t.callback, t.name) + end, + } + elseif lwc.context and not t.lowlevel then + return { + -- Register the callback when the table is created, + -- but activate it when `enable()` is called. + enable = nodes.tasks.appendaction(t.category, t.position, "lwc." .. t.name) + or function() + nodes.tasks.enableaction(t.category, "lwc." .. t.name) + end, + disable = function() + nodes.tasks.disableaction(t.category, "lwc." .. t.name) + end, + } + elseif lwc.context and t.lowlevel then + --[[ + Some of the callbacks in \ConTeXt{} have no associated "actions". Unlike + with \LuaTeX{}base, \ConTeXt{} leaves some \LuaTeX{} callbacks unregistered + and unfrozen. Because of this, we need to register some callbacks at the + engine level. This is fragile though, because a future \ConTeXt{} update + may decide to register one of these functions, in which case + \lwc/ will crash with a cryptic error message. + ]] + return { + enable = function() callback.register(t.callback, t.func) end, + disable = function() callback.register(t.callback, nil) end, + } + end +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 + lwc.callbacks.disable_box_warnings.enable() + + -- Ensure that we were actually given a par (only under \ConTeXt{} for some reason) + if head.id ~= par_id and lwc.context then + return head + end + + -- We need to return the unmodified head at the end, so we make a copy here + local new_head = copy(head) + + -- Prevent ultra-short last lines (\TeX{}Book p. 104), except with narrow columns + local parfillskip = last(new_head) + if parfillskip.id == glue_id and tex.hsize > min_col_width then + parfillskip.stretch_order = 0 + parfillskip.stretch = 0.9 * tex.hsize -- Last line must be at least 10% long + end + + -- Break the paragraph 1 line longer than natural + local long_node, long_info = tex.linebreak(new_head, { + looseness = 1, + emergencystretch = tex.dimen.lwcemergencystretch, + }) + + -- Break the natural paragraph so we know how long it was + local natural_node, natural_info = tex.linebreak(copy(head)) + flush_list(natural_node) + + lwc.callbacks.disable_box_warnings.disable() + + -- 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 = -1 * long_info.prevdepth + last(long_node).next = prevdepth + + table.insert(lwc.paragraphs, {demerits = long_demerits, node = long_node}) + + -- \LuaMetaTeX{} crashes if we return `true` + return head +end + + +--- Tags the beginning and the end of each paragraph as it is added to the page. +--- +--- We add an attribute to the first and last node of each paragraph. The ID is +--- some arbitrary number for \lwc/, and the value corresponds to the +--- paragraphs index, which is negated for the end of the paragraph. +function lwc.mark_paragraphs(head) + set_attribute(head, lwc.attribute, #lwc.paragraphs) + set_attribute(last(head), lwc.attribute, -1 * #lwc.paragraphs) + + return head +end + + +--- Remove the widows and orphans from the page, just after the output routine. +--- +--- This function holds the "meat" of the module. It is called just after the +--- end of the output routine, before the page is shipped out. If the output +--- penalty indicates that the page was broken at a widow or an orphan, we +--- 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 penalty = tex.outputpenalty + local paragraphs = lwc.paragraphs + + --[[ + We only need to process pages that have orphans or widows. If `paragraphs` + is empty, then there is nothing that we can do. + ]] + if penalty >= 10000 or + penalty <= -10000 or + penalty == 0 or + #paragraphs == 0 then + return head + end + + 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. + ]] + local paragraph_index = 1 + local minimum_demerits = paragraphs[paragraph_index].demerits + + -- 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 + flush_list(paragraphs[paragraph_index].node) + paragraphs[paragraph_index].node = nil + paragraph_index, minimum_demerits = i, paragraph.demerits + elseif i > 1 then + -- Not sure why `i > 1` is required? + flush_list(paragraph.node) + paragraph.node = nil + end + end + + local target_node = paragraphs[paragraph_index].node + local clear_flag = false + + -- Loop through all of the nodes on the page + while head do + -- Insert the start of the replacement paragraph + if has_attribute(head, lwc.attribute, paragraph_index) then + head.prev.next = target_node + clear_flag = true + end + + -- Insert the end of the replacement paragraph + if has_attribute(head, lwc.attribute, -1 * paragraph_index) then + last(target_node).next = head.next + clear_flag = false + end + + -- Start of final paragraph + if has_attribute(head, lwc.attribute, #paragraphs) then + local last_line = copy(last(head)) + + last(last_line).next = copy(tex.lists[lwc.contrib_head]) + + last(head).prev.prev.next = nil + -- Move the last line to the next page + tex.lists[lwc.contrib_head] = last_line + end + + if clear_flag then + head = node.free(head) + else + head = head.next + end + end + + lwc.paragraphs = {} -- Clear paragraphs array at the end of the page + + return head_save +end + + +-- Add all of the callbacks +lwc.callbacks = { + disable_box_warnings = lwc.register_callback({ + callback = "hpack_quality", + func = function() end, + name = "disable_box_warnings", + lowlevel = true, + }), + remove_widows = lwc.register_callback({ + callback = "pre_output_filter", + func = lwc.remove_widows, + name = "remove_widows", + lowlevel = true, + }), + save_paragraphs = lwc.register_callback({ + callback = "pre_linebreak_filter", + func = lwc.save_paragraphs, + name = "save_paragraphs", + category = "processors", + position = "after", + }), + mark_paragraphs = lwc.register_callback({ + callback = "post_linebreak_filter", + func = lwc.mark_paragraphs, + name = "mark_paragraphs", + category = "finalizers", + position = "after", + }), +} + + +local enabled = false +function lwc.enable_callbacks() + if not enabled then + lwc.callbacks.remove_widows.enable() + lwc.callbacks.save_paragraphs.enable() + lwc.callbacks.mark_paragraphs.enable() + + enabled = true + else + lwc.warning("Already enabled") + end +end + + +function lwc.disable_callbacks() + if enabled then + lwc.callbacks.save_paragraphs.disable() + lwc.callbacks.mark_paragraphs.disable() + --[[ + We do \emph{not} disable `remove_widows` callback, since we still want + to expand any of the previously-saved paragraphs if we hit an orphan + or a widow. + ]] + + enabled = false + else + lwc.warning("Already disabled") + end +end + + +return lwc diff --git a/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.tex b/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.tex new file mode 100644 index 00000000000..fc10ff0ebb6 --- /dev/null +++ b/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.tex @@ -0,0 +1,33 @@ +% lua-widow-control +% https://github.com/gucci-on-fleek/lua-widow-control +% SPDX-License-Identifier: MPL-2.0+ +% SPDX-FileCopyrightText: 2021 gucci-on-fleek + +\wlog{lua-widow-control v1.0.0} %%version + +\input ltluatex % \LuaTeX{}Base + +\clubpenalty=1 +\widowpenalty=1 +\displaywidowpenalty=0 +\interlinepenalty=0 +\brokenpenalty=0 + +\newdimen\lwcemergencystretch +\lwcemergencystretch=3em + +\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. +\expandglyphsinfont\the\font 20 20 5 +\adjustspacing=2 + +\def\lwcenable{\directlua{lwc.enable_callbacks()}} +\def\lwcdisable{\directlua{lwc.disable_callbacks()}} + +% Enable \lwc/ by default when the package is loaded. +\lwcenable + +\endinput diff --git a/Master/tlpkg/bin/tlpkg-ctan-check b/Master/tlpkg/bin/tlpkg-ctan-check index 00c5d8b1a05..b18fc21f080 100755 --- a/Master/tlpkg/bin/tlpkg-ctan-check +++ b/Master/tlpkg/bin/tlpkg-ctan-check @@ -486,7 +486,7 @@ my @TLP_working = qw( ltxcmds ltxdockit ltxfileinfo ltxguidex ltximg ltxkeys ltxmisc ltxnew ltxtools lua-alt-getopt lua-check-hyphen lua-physical lua-typo lua-uca lua-ul - lua-uni-algos lua-visual-debug + lua-uni-algos lua-visual-debug lua-widow-control luabibentry luabidi luacode luacolor luahyphenrules luaimageembed luaindex luainputenc luaintro luakeys lualatex-doc lualatex-doc-de diff --git a/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc b/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc index 93ef0e7d8ec..808559f66c7 100644 --- a/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc +++ b/Master/tlpkg/tlpsrc/collection-luatex.tlpsrc @@ -28,6 +28,7 @@ depend lua-uca depend lua-uni-algos depend lua-ul depend lua-visual-debug +depend lua-widow-control depend luacode depend luacolor depend luahyphenrules diff --git a/Master/tlpkg/tlpsrc/lua-widow-control.tlpsrc b/Master/tlpkg/tlpsrc/lua-widow-control.tlpsrc new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/Master/tlpkg/tlpsrc/lua-widow-control.tlpsrc |