diff options
Diffstat (limited to 'Master/texmf-dist/tex')
7 files changed, 698 insertions, 210 deletions
diff --git a/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkiv b/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkiv index 6eff5c9b00c..e24f972d577 100644 --- a/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkiv +++ b/Master/texmf-dist/tex/context/third/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/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 index 6eff5c9b00c..e24f972d577 100644 --- 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 @@ -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/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control-2022-02-22.sty b/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control-2022-02-22.sty new file mode 100644 index 00000000000..783db59ed22 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/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/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 index 9ef5a76de56..56462e40d87 100644 --- 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 @@ -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/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 index f1ecb54c65f..5801feef129 100644 --- 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 @@ -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/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 index d83e6114f4f..7a75e52132f 100644 --- 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 @@ -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/Master/texmf-dist/tex/optex/lua-widow-control/lua-widow-control.opm b/Master/texmf-dist/tex/optex/lua-widow-control/lua-widow-control.opm index 4b8837cfec9..992d817a5c8 100644 --- a/Master/texmf-dist/tex/optex/lua-widow-control/lua-widow-control.opm +++ b/Master/texmf-dist/tex/optex/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 |