summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/lua-widow-control
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/luatex/generic/lua-widow-control
parentb1f3f2681b2bf35570ad11f65270777f38c55f7e (diff)
CTAN sync 202203090301
Diffstat (limited to 'macros/luatex/generic/lua-widow-control')
-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
11 files changed, 902 insertions, 268 deletions
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