summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/lua-widow-control/lua-widow-control.lua
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/generic/lua-widow-control/lua-widow-control.lua')
-rw-r--r--macros/luatex/generic/lua-widow-control/lua-widow-control.lua225
1 files changed, 125 insertions, 100 deletions
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 9304bf9a4d..f1ecb54c65 100644
--- a/macros/luatex/generic/lua-widow-control/lua-widow-control.lua
+++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.lua
@@ -2,7 +2,7 @@
lua-widow-control
https://github.com/gucci-on-fleek/lua-widow-control
SPDX-License-Identifier: MPL-2.0+
- SPDX-FileCopyrightText: 2021 Max Chernoff
+ SPDX-FileCopyrightText: 2022 Max Chernoff
]]
lwc = {}
@@ -14,13 +14,19 @@ lwc.name = "lua-widow-control"
detect the format name then set some flags for later processing.
]]
local format = tex.formatname
+local context, latex, plain, optex, lmtx
if format:find('cont') then -- cont-en, cont-fr, cont-nl, ...
- lwc.context = true
+ context = true
+ if status.luatex_engine == "luametatex" then
+ lmtx = true
+ end
elseif format:find('latex') then -- lualatex, lualatex-dev, ...
- lwc.latex = true
+ latex = true
elseif format == 'luatex' then -- Plain
- lwc.plain = true
+ plain = true
+elseif format == 'optex' then -- OpTeX
+ optex = true
end
--[[
@@ -29,7 +35,7 @@ end
]]
local last = node.slide
local copy = node.copy_list
-local par_id = node.id("par")
+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
@@ -39,36 +45,29 @@ local min_col_width = tex.sp("25em")
local maxdimen = 1073741823 -- \\maxdimen in sp
--[[
- This error is raised in the following circumstances:
- - When the user manually loads the Lua module without loading Lua\TeX{}Base
- - When the package is used with an unsupported format
- Both of these are pretty unlikely, but it can't hurt to check.
- ]]
-assert(lwc.context or luatexbase, [[
-
- This module requires a supported callback library. Please
- follow the following format-dependant instructions:
- - LaTeX: Use a version built after 2015-01-01, or include
- `\usepackage{luatexbase}' before loading this module.
- - Plain: Include `\input ltluatex' before loading this module.
- - ConTeXt: Use the LMTX version.
-]])
-
---[[
Package/module initialization
]]
-if lwc.context then
- lwc.warning = logs.reporter("module", lwc.name)
- lwc.info = logs.reporter("module", lwc.name)
- lwc.attribute = attributes.public(lwc.name)
- lwc.contrib_head = 'contributehead' -- For \LuaMetaTeX{}
- lwc.stretch_order = "stretchorder"
- lwc.pagenum = function () return tex.count["realpageno"] end
-elseif lwc.plain or lwc.latex then
+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{}
+ stretch_order = "stretchorder"
+ pagenum = function() return tex.count["realpageno"] end
+elseif context and not lmtx then
+ 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/15", --%%date
- version = "1.1.5", --%%version
+ date = "2022/02/22", --%%date
+ version = "1.1.6", --%%version
description = [[
This module provides a LuaTeX-based solution to prevent
@@ -77,29 +76,30 @@ elseif lwc.plain or lwc.latex then
paragraphs.
]],
}
- lwc.warning = function(str) luatexbase.module_warning(lwc.name, str) end
- lwc.info = function(str) luatexbase.module_info(lwc.name, str) end
- lwc.attribute = luatexbase.new_attribute(lwc.name)
- lwc.contrib_head = 'contrib_head' -- For \LuaTeX{}
- lwc.stretch_order = "stretch_order"
- lwc.pagenum = function () return tex.count[0] end
+ 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"
+ pagenum = function() return tex.count[0] end
else -- uh oh
error [[
Unsupported format.
- Please use (Lua)LaTeX, Plain (Lua)TeX, or ConTeXt (MKXL/LMTX)
+ Please use (Lua)LaTeX, Plain (Lua)TeX, ConTeXt (MKXL/LMTX),
+ or OpTeX.
]]
end
-lwc.paragraphs = {} -- List to hold the alternate paragraph versions
-
-if tex.interlinepenalty ~= 0 then
- lwc.warning [[
-\interlinepenalty is set to a non-zero value.
-This may prevent lua-widow-control from
-properly functioning.
-]]
-end
+local paragraphs = {} -- List to hold the alternate paragraph versions
--[[
@@ -118,8 +118,8 @@ end
--- @return table t Enablers/Disablers for the callback
--- enable: function = Enable the callback
--- disable: function = Disable the callback
-function lwc.register_callback(t)
- if lwc.plain or lwc.latex then -- Both use \LuaTeX{}Base for callbacks
+local function register_callback(t)
+ if plain or latex then -- Both use \LuaTeX{}Base for callbacks
return {
enable = function()
luatexbase.add_to_callback(t.callback, t.func, t.name)
@@ -128,19 +128,19 @@ function lwc.register_callback(t)
luatexbase.remove_from_callback(t.callback, t.name)
end,
}
- elseif lwc.context and not t.lowlevel then
+ elseif context and not t.lowlevel then
return {
-- Register the callback when the table is created,
-- but activate it when `enable()` is called.
enable = nodes.tasks.appendaction(t.category, t.position, "lwc." .. t.name)
- or function()
+ or function()
nodes.tasks.enableaction(t.category, "lwc." .. t.name)
- end,
+ end,
disable = function()
nodes.tasks.disableaction(t.category, "lwc." .. t.name)
end,
}
- elseif lwc.context and t.lowlevel then
+ elseif context and t.lowlevel then
--[[
Some of the callbacks in \ConTeXt{} have no associated "actions". Unlike
with \LuaTeX{}base, \ConTeXt{} leaves some \LuaTeX{} callbacks unregistered
@@ -153,17 +153,31 @@ function lwc.register_callback(t)
enable = function() callback.register(t.callback, t.func) end,
disable = function() callback.register(t.callback, nil) end,
}
+ elseif optex then
+ return {
+ enable = function()
+ callback.add_to_callback(t.callback, t.func, t.name)
+ end,
+ disable = function()
+ callback.remove_from_callback(t.callback, t.name)
+ end,
+ }
end
end
-
--- Saves each paragraph, but lengthened by 1 line
function lwc.save_paragraphs(head)
-- Prevent the "underfull hbox" warnings when we store a potential paragraph
- lwc.callbacks.disable_box_warnings.enable()
+ local renable_box_warnings
+ if (latex or plain) and
+ #luatexbase.callback_descriptions("hpack_quality") == 0
+ then -- See #18 and michal-h21/linebreaker#3
+ renable_box_warnings = true
+ lwc.callbacks.disable_box_warnings.enable()
+ end
-- Ensure that we were actually given a par (only under \ConTeXt{} for some reason)
- if head.id ~= par_id and lwc.context then
+ if head.id ~= par_id and context then
return head
end
@@ -173,8 +187,8 @@ function lwc.save_paragraphs(head)
-- Prevent ultra-short last lines (\TeX{}Book p. 104), except with narrow columns
local parfillskip = last(new_head)
if parfillskip.id == glue_id and tex.hsize > min_col_width then
- parfillskip[lwc.stretch_order] = 0
- parfillskip.stretch = 0.8 * tex.hsize -- Last line must be at least 20% long
+ parfillskip[stretch_order] = 0
+ parfillskip.stretch = 0.8 * tex.hsize -- Last line must be at least 20% long
end
-- Break the paragraph 1 line longer than natural
@@ -187,7 +201,9 @@ function lwc.save_paragraphs(head)
local natural_node, natural_info = tex.linebreak(copy(head))
flush_list(natural_node)
- lwc.callbacks.disable_box_warnings.disable()
+ if renable_box_warnings then
+ lwc.callbacks.disable_box_warnings.disable()
+ end
-- If we can't lengthen the paragraph, assign a \emph{very} large demerit value
local long_demerits
@@ -202,26 +218,24 @@ function lwc.save_paragraphs(head)
prevdepth.width = natural_info.prevdepth - long_info.prevdepth
last(long_node).next = prevdepth
- table.insert(lwc.paragraphs, {demerits = long_demerits, node = long_node})
+ table.insert(paragraphs, { demerits = long_demerits, node = long_node })
-- \LuaMetaTeX{} crashes if we return `true`
return head
end
-
--- Tags the beginning and the end of each paragraph as it is added to the page.
---
--- We add an attribute to the first and last node of each paragraph. The ID is
--- some arbitrary number for \lwc/, and the value corresponds to the
--- paragraphs index, which is negated for the end of the paragraph.
function lwc.mark_paragraphs(head)
- set_attribute(head, lwc.attribute, #lwc.paragraphs)
- set_attribute(last(head), lwc.attribute, -1 * #lwc.paragraphs)
+ set_attribute(head, attribute, #paragraphs)
+ set_attribute(last(head), attribute, -1 * #paragraphs)
return head
end
-
--- A "safe" version of the last/slide function.
---
--- Sometimes the node list can form a loop. Since there is no last element
@@ -236,7 +250,7 @@ local function safe_last(head)
local id = node.is_node(head) -- Returns the internal node id
if ids[id] then
- lwc.warning [[Circular node list detected!
+ 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.]]
@@ -254,7 +268,6 @@ affected paragraph or change the page breaks in your document.]]
return head
end
-
--- Remove the widows and orphans from the page, just after the output routine.
---
--- This function holds the "meat" of the module. It is called just after the
@@ -263,21 +276,36 @@ 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 penalty = tex.outputpenalty
- local paragraphs = lwc.paragraphs
+ local penalty = tex.outputpenalty - tex.interlinepenalty
+ local widowpenalty = tex.widowpenalty
+ local clubpenalty = tex.clubpenalty
+ local displaywidowpenalty = tex.displaywidowpenalty
+ local brokenpenalty = tex.brokenpenalty
--[[
We only need to process pages that have orphans or widows. If `paragraphs`
is empty, then there is nothing that we can do.
+
+ The list of penalties is from:
+ https://tug.org/TUGboat/tb39-3/tb123mitt-widows-code.pdf#subsection.0.2.1
]]
- if penalty >= 10000 or
- penalty <= -10000 or
- penalty == 0 or
- #paragraphs == 0 then
- return head
+ if (penalty == widowpenalty or
+ penalty == displaywidowpenalty or
+ penalty == clubpenalty or
+ penalty == clubpenalty + widowpenalty or
+ penalty == clubpenalty + displaywidowpenalty or
+ penalty == brokenpenalty or
+ penalty == brokenpenalty + widowpenalty or
+ penalty == brokenpenalty + displaywidowpenalty or
+ penalty == brokenpenalty + clubpenalty or
+ penalty == brokenpenalty + clubpenalty + widowpenalty or
+ penalty == brokenpenalty + clubpenalty + displaywidowpenalty) and
+ #paragraphs >= 1 then
+ else
+ return head
end
- lwc.info("Widow/orphan detected. Attempting to remove.")
+ info("Widow/orphan detected. Attempting to remove.")
local head_save = head -- Save the start of the `head` linked-list
@@ -309,7 +337,7 @@ function lwc.remove_widows(head)
-- Loop through all of the nodes on the page
while head do
-- Insert the start of the replacement paragraph
- if has_attribute(head, lwc.attribute, paragraph_index) then
+ if has_attribute(head, attribute, paragraph_index) then
safe_last(target_node) -- Remove any loops
head.prev.next = target_node
@@ -317,25 +345,25 @@ function lwc.remove_widows(head)
end
-- Insert the end of the replacement paragraph
- if has_attribute(head, lwc.attribute, -1 * paragraph_index) then
+ if has_attribute(head, attribute, -1 * paragraph_index) then
safe_last(target_node).next = head.next
clear_flag = false
end
-- Start of final paragraph
- if has_attribute(head, lwc.attribute, #paragraphs) then
+ if has_attribute(head, attribute, #paragraphs) then
local last_line = copy(last(head))
- last(last_line).next = copy(tex.lists[lwc.contrib_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[lwc.contrib_head] = last_line
- lwc.info(
- "Widow/orphan successfully removed at paragraph "
+ tex.lists[contrib_head] = last_line
+ info(
+ "Widow/orphan successfully removed at paragraph "
.. paragraph_index
.. " on page "
- .. lwc.pagenum()
+ .. pagenum()
.. "."
)
end
@@ -347,37 +375,36 @@ function lwc.remove_widows(head)
end
end
- lwc.paragraphs = {} -- Clear paragraphs array at the end of the page
+ paragraphs = {} -- Clear paragraphs array at the end of the page
return head_save
end
-
-- Add all of the callbacks
lwc.callbacks = {
- disable_box_warnings = lwc.register_callback({
+ disable_box_warnings = register_callback({
callback = "hpack_quality",
- func = function() end,
- name = "disable_box_warnings",
+ func = function() end,
+ name = "disable_box_warnings",
lowlevel = true,
}),
- remove_widows = lwc.register_callback({
+ remove_widows = register_callback({
callback = "pre_output_filter",
- func = lwc.remove_widows,
- name = "remove_widows",
+ func = lwc.remove_widows,
+ name = "remove_widows",
lowlevel = true,
}),
- save_paragraphs = lwc.register_callback({
+ save_paragraphs = register_callback({
callback = "pre_linebreak_filter",
- func = lwc.save_paragraphs,
- name = "save_paragraphs",
+ func = lwc.save_paragraphs,
+ name = "save_paragraphs",
category = "processors",
position = "after",
}),
- mark_paragraphs = lwc.register_callback({
+ mark_paragraphs = register_callback({
callback = "post_linebreak_filter",
- func = lwc.mark_paragraphs,
- name = "mark_paragraphs",
+ func = lwc.mark_paragraphs,
+ name = "mark_paragraphs",
category = "finalizers",
position = "after",
}),
@@ -393,11 +420,10 @@ function lwc.enable_callbacks()
enabled = true
else
- lwc.warning("Already enabled")
+ warning("Already enabled")
end
end
-
function lwc.disable_callbacks()
if enabled then
lwc.callbacks.save_paragraphs.disable()
@@ -410,7 +436,7 @@ function lwc.disable_callbacks()
enabled = false
else
- lwc.warning("Already disabled")
+ warning("Already disabled")
end
end
@@ -422,5 +448,4 @@ function lwc.if_lwc_enabled()
end
end
-
return lwc