diff options
author | Norbert Preining <norbert@preining.info> | 2022-05-15 03:00:49 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2022-05-15 03:00:49 +0000 |
commit | f8e05edb088c3b5246e016f68df9c4cadccfd34d (patch) | |
tree | bc2ad458c483af921298a66be3b70d0cc74f2c85 /macros/luatex | |
parent | 2afee5e322fb0dfc2764c270803bc6381b65636e (diff) |
CTAN sync 202205150300
Diffstat (limited to 'macros/luatex')
10 files changed, 580 insertions, 254 deletions
diff --git a/macros/luatex/generic/lua-widow-control/README.md b/macros/luatex/generic/lua-widow-control/README.md index 77dd7734da..cc6aed7ba0 100644 --- a/macros/luatex/generic/lua-widow-control/README.md +++ b/macros/luatex/generic/lua-widow-control/README.md @@ -48,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. --- -_v2.0.6 (2022-04-23)_ <!--%%version %%dashdate--> +_v2.1.0 (2022-05-14)_ <!--%%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 index e8b2eefb0a..d67c68d2fd 100644 --- 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 @@ -7,6 +7,15 @@ \ProvidesPackage{lua-widow-control}% [2022/02/22 v1.1.6] +% The version number above is somewhat-misleading: I will make bugfixes to this file +% from time to time, but the core of the file will not change. Therefore, we should +% report a real version number here for debugging. +\PackageInfo{lua-widow-control}{% + Real version: + 2022/05/14 %%dashdate + v2.1.0 %%version +} + \PackageWarning{lua-widow-control}{% Old LaTeX format detected!\MessageBreak\MessageBreak Lua-widow-control prefers a LaTeX format\MessageBreak 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 94dd85e145..45360db209 100644 --- a/macros/luatex/generic/lua-widow-control/lua-widow-control.lua +++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.lua @@ -5,21 +5,45 @@ SPDX-FileCopyrightText: 2022 Max Chernoff ]] +--- Tell the linter about node attributes +--- @class node +--- @field prev node +--- @field next node +--- @field id integer +--- @field subtype integer +--- @field penalty integer +--- @field height integer +--- @field depth integer + +-- Set some default variables lwc = lwc or {} lwc.name = "lua-widow-control" lwc.nobreak_behaviour = "keep" +-- Locals for `debug_print` local write_nl = texio.write_nl local string_rep = string.rep +local write_log +if status.luatex_engine == "luametatex" then + write_log = "logfile" +else + write_log = "log" +end + +--- Prints debugging messages to the log, only if `debug` is set to `true`. +--- @param title string The "title" to use +--- @param text string? The "content" to print +--- @return nil local function debug_print(title, text) if not lwc.debug then return end + -- The number of spaces we need local filler = 15 - #title if text then - write_nl("log", "LWC (" .. title .. string_rep(" ", filler) .. "): " .. text) + write_nl(write_log, "LWC (" .. title .. string_rep(" ", filler) .. "): " .. text) else - write_nl("log", "LWC: " .. string_rep(" ", 18) .. title) + write_nl(write_log, "LWC: " .. string_rep(" ", 18) .. title) end end @@ -31,16 +55,16 @@ end local format = tex.formatname local context, latex, plain, optex, lmtx -if format:find('cont') then -- cont-en, cont-fr, cont-nl, ... +if format:find("cont") then -- cont-en, cont-fr, cont-nl, ... context = true if status.luatex_engine == "luametatex" then lmtx = true end -elseif format:find('latex') then -- lualatex, lualatex-dev, ... +elseif format:find("latex") then -- lualatex, lualatex-dev, ... latex = true -elseif format == 'luatex' then -- Plain +elseif format == "luatex" then -- Plain plain = true -elseif format == 'optex' then -- OpTeX +elseif format == "optex" then -- OpTeX optex = true end @@ -48,46 +72,81 @@ end Save some local copies of the node library to reduce table lookups. This is probably a useless micro-optimization, but it can't hurt. ]] -local last = node.slide -local copy = node.copy_list or node.copylist -local par_id = node.id("par") or node.id("local_par") +-- Node ID's +local baselineskip_subid = 2 local glue_id = node.id("glue") 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 line_subid = 1 +local linebreakpenalty_subid = 1 +local par_id = node.id("par") or node.id("local_par") +local penalty_id = node.id("penalty") + +-- Local versions of globals +local copy = node.copy_list or node.copylist local find_attribute = node.find_attribute or node.findattribute local flush_list = node.flush_list or node.flushlist local free = node.free +local getattribute = node.get_attribute or node.getattribute +local insert_token = token.put_next or token.putnext +local last = node.slide +local new_node = node.new local node_id = node.is_node or node.isnode +local set_attribute = node.set_attribute or node.setattribute +local string_char = string.char +local traverse = node.traverse +local traverseid = node.traverse_id or node.traverseid + +-- Misc. Constants +local iffalse = token.create("iffalse") +local iftrue = token.create("iftrue") +local INFINITY = 10000 local min_col_width = tex.sp("250pt") +local SINGLE_LINE = 50 +local PAGE_MULTIPLE = 100 --[[ Package/module initialization ]] -local warning, info, attribute, contrib_head, stretch_order, pagenum, emergencystretch, max_cost +local attribute, + contrib_head, + emergencystretch, + info, + max_cost, + pagenum, + stretch_order, + warning if lmtx then + -- LMTX has removed underscores from most of the Lua parts debug_print("LMTX") - contrib_head = 'contributehead' + contrib_head = "contributehead" stretch_order = "stretchorder" else - contrib_head = 'contrib_head' + 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) + + warning = logs.reporter(lwc.name, "warning") + local _info = logs.reporter(lwc.name, "info") + info = function (text) + logs.pushtarget("logfile") + _info(text) + logs.poptarget() + end attribute = attributes.public(lwc.name) pagenum = function() return tex.count["realpageno"] end + + -- Dimen names emergencystretch = "lwc_emergency_stretch" max_cost = "lwc_max_cost" elseif plain or latex or optex then pagenum = function() return tex.count[0] end + -- Dimen names if tex.isdimen("g__lwc_emergencystretch_dim") then emergencystretch = "g__lwc_emergencystretch_dim" max_cost = "g__lwc_maxcost_int" @@ -100,8 +159,8 @@ elseif plain or latex or optex then debug_print("Plain/LaTeX") luatexbase.provides_module { name = lwc.name, - date = "2022/04/23", --%%slashdate - version = "2.0.6", --%%version + date = "2022/05/14", --%%dashdate + version = "2.1.0", --%%version description = [[ This module provides a LuaTeX-based solution to prevent @@ -114,11 +173,12 @@ paragraphs.]], 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 +else -- This shouldn't ever happen error [[Unsupported format. Please use LaTeX, Plain TeX, ConTeXt or OpTeX.]] @@ -126,6 +186,8 @@ end local paragraphs = {} -- List to hold the alternate paragraph versions +--- Gets the current paragraph and page locations +--- @return string local function get_location() return "At " .. pagenum() .. "/" .. #paragraphs end @@ -134,78 +196,22 @@ end Function definitions ]] ---- Create a table of functions to enable or disable a given callback ---- @param t table Parameters of the callback to create ---- callback: str = The \LuaTeX{} callback name ---- func: function = The function to call ---- name: str = The name/ID of the callback ---- category: str = The category for a \ConTeXt{} "Action" ---- position: str = The "position" for a \ConTeXt{} "Action" ---- lowlevel: bool = If we should use a lowlevel \LuaTeX{} callback instead of a ---- \ConTeXt{} "Action" ---- @return table t Enablers/Disablers for the callback ---- enable: function = Enable the callback ---- disable: function = Disable the callback -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) - end, - disable = function() - luatexbase.remove_from_callback(t.callback, t.name) - end, - } - 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() - nodes.tasks.enableaction(t.category, "lwc." .. t.name) - end, - disable = function() - nodes.tasks.disableaction(t.category, "lwc." .. t.name) - end, - } - 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 - and unfrozen. Because of this, we need to register some callbacks at the - engine level. This is fragile though, because a future \ConTeXt{} update - may decide to register one of these functions, in which case - \lwc/ will crash with a cryptic error message. - ]] - return { - enable = function() callback.register(t.callback, t.func) end, - disable = function() callback.register(t.callback, nil) end, - } - 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 - +--- Prints the initial glyphs and glue of an hlist +--- @param head node +--- @return nil 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) + if n.char < 127 then -- Only ASCII + chars = chars .. string_char(n.char) else - chars = chars .. "#" + chars = chars .. "#" -- Replacement for an unknown glyph end elseif n.id == glue_id then - chars = chars .. " " + chars = chars .. " " -- Any glue goes to a space end if #chars > 25 then break @@ -215,14 +221,56 @@ local function get_chars(head) debug_print(get_location(), chars) end +--- The "cost function" to use. See the manual. +--- @param demerits number The demerits of the broken paragraph +--- @param lines number The number of lines in the broken paragraph +--- @return number The cost of the broken paragraph function lwc.paragraph_cost(demerits, lines) return demerits / math.sqrt(lines) end +--- Checks if the ConTeXt "grid snapping" is active +--- @return boolean +local function grid_mode_enabled() + -- Compare the token "mode" to see if `\\ifgridsnapping` is `\\iftrue` + return token.create("ifgridsnapping").mode == iftrue.mode +end + +--- Gets the next node of a type/subtype in a node list +--- @param head node The head of the node list +--- @param id number The node type +--- @param args table? +--- subtype: number = The node subtype +--- reverse: bool = Whether we should iterate backwards +--- @return node +local function next_of_type(head, id, args) + args = args or {} + if lmtx or not args.reverse then + for n, subtype in traverseid(id, head, args.reverse) do + if (subtype == args.subtype) or (args.subtype == nil) then + return n + end + end + else -- Only LMTX has the built-in backwards traverser + while head do + if head.id == id and + (head.subtype == args.subtype or args.subtype == nil) + then + return head + end + head = head.prev + end + end +end + --- Saves each paragraph, but lengthened by 1 line +--- +--- Called by the `pre_linebreak_filter` callback +--- +--- @param head node +--- @return node function lwc.save_paragraphs(head) - -- Ensure that we were actually given a par (only under \ConTeXt{} for some reason) - if (head.id ~= par_id and context) or + if (head.id ~= par_id and context) or -- Ensure that we were actually given a par status.output_active -- Don't run during the output routine then return head @@ -231,23 +279,34 @@ function lwc.save_paragraphs(head) -- Prevent the "underfull hbox" warnings when we store a potential paragraph local renable_box_warnings if (context or optex) or - #luatexbase.callback_descriptions("hpack_quality") == 0 + #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 - -- We need to return the unmodified head at the end, so we make a copy here local new_head = copy(head) -- Prevent ultra-short last lines (\TeX{}Book p. 104), except with narrow columns - local parfillskip = last(new_head) - if parfillskip.id == glue_id and tex.hsize > min_col_width then + -- Equivalent to \\parfillskip=0pt plus 0.8\\hsize + local parfillskip + if lmtx or last(new_head).id ~= glue_id then + -- LMTX does not automatically add the \\parfillskip glue + parfillskip = new_node("glue", "parfillskip") + else + parfillskip = last(new_head) + end + + if tex.hsize > min_col_width then parfillskip[stretch_order] = 0 parfillskip.stretch = 0.8 * tex.hsize -- Last line must be at least 20% long end + if lmtx or last(new_head).id ~= glue_id then + last(new_head).next = parfillskip + end + -- Break the paragraph 1 line longer than natural local long_node, long_info = tex.linebreak(new_head, { looseness = 1, @@ -255,25 +314,41 @@ function lwc.save_paragraphs(head) }) -- Break the natural paragraph so we know how long it was - local natural_node, natural_info = tex.linebreak(copy(head)) + nat_head = copy(head) + + if lmtx then + parfillskip = new_node("glue", "parfillskip") + parfillskip[stretch_order] = 1 + parfillskip.stretch = 1 -- 0pt plus 1fil + last(nat_head).next = parfillskip + end + + local natural_node, natural_info = tex.linebreak(nat_head) flush_list(natural_node) if renable_box_warnings then lwc.callbacks.disable_box_warnings.disable() end - -- Offset the accumulated \\prevdepth - local prevdepth = node.new("glue") - prevdepth.width = natural_info.prevdepth - long_info.prevdepth - last(long_node).next = prevdepth + if not grid_mode_enabled() then + -- Offset the accumulated \\prevdepth + local prevdepth = new_node("glue") + prevdepth.width = natural_info.prevdepth - long_info.prevdepth + last(long_node).next = prevdepth + end + + local long_cost = lwc.paragraph_cost(long_info.demerits, long_info.prevgraf) - if long_info.prevgraf == natural_info.prevgraf + 1 then + if long_info.prevgraf == natural_info.prevgraf + 1 and + long_cost > 10 -- Any paragraph that is "free" to expand is suspicious + then table.insert(paragraphs, { - cost = lwc.paragraph_cost(long_info.demerits, long_info.prevgraf), - node = long_node + cost = long_cost, + node = next_of_type(long_node, hlist_id, { subtype = line_subid }) }) end + -- Print some debugging information get_chars(head) debug_print(get_location(), "nat lines " .. natural_info.prevgraf) debug_print( @@ -288,21 +363,45 @@ function lwc.save_paragraphs(head) lwc.paragraph_cost(long_info.demerits, long_info.prevgraf) ) - -- \LuaMetaTeX{} crashes if we return `true` + -- \ConTeXt{} crashes if we return `true` return head end -local last_paragraph = 0 --- 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. +--- paragraphs index, which is negated for the end of the paragraph. Called by the +--- `post_linebreak_filter` callback. +--- +--- @param head node +--- @return node function lwc.mark_paragraphs(head) - if not status.output_active then - set_attribute(head, attribute, #paragraphs + (100 * pagenum())) - set_attribute(last(head), attribute, -1 * (#paragraphs + (100 * pagenum()))) - last_paragraph = #paragraphs + if not status.output_active then -- Don't run during the output routine + -- Get the start and end of the paragraph + local top_para = next_of_type(head, hlist_id, { subtype = line_subid }) + local bottom_para = last(head) + + if top_para ~= bottom_para then + set_attribute( + top_para, + attribute, + #paragraphs + (PAGE_MULTIPLE * pagenum()) + ) + set_attribute( + bottom_para, + attribute, + -1 * (#paragraphs + (PAGE_MULTIPLE * pagenum())) + ) + else + -- We need a special tag for a 1-line paragraph since the node can only + -- have one attribute value + set_attribute( + top_para, + attribute, + #paragraphs + (PAGE_MULTIPLE * pagenum()) + SINGLE_LINE + ) + end end return head @@ -313,7 +412,11 @@ end --- Sometimes the node list can form a loop. Since there is no last element --- of a looped linked-list, the `last()` function will never terminate. This --- function provides a "safe" version of the `last()` function that will break ---- the loop at the end if the list is circular. +--- the loop at the end if the list is circular. Called by the `pre_output_filter` +--- callback. +--- +--- @param head node The start of a node list +--- @return node The last node in a list local function safe_last(head) local ids = {} local prev @@ -341,24 +444,15 @@ recover, but your output may be corrupted. return head end ---- Remove the widows and orphans from the page, just after the output routine. ---- ---- This function holds the "meat" of the module. It is called just after the ---- end of the output routine, before the page is shipped out. If the output ---- penalty indicates that the page was broken at a widow or an orphan, we ---- replace one paragraph with the same paragraph, but lengthened by one line. ---- Then, we can push the bottom line of the page to the next page. -function lwc.remove_widows(head) - local head_save = head -- Save the start of the `head` linked-list - - local penalty = tex.outputpenalty - tex.interlinepenalty +--- Checks to see if a penalty matches the widow/orphan/broken penalties +--- @param penalty number +--- @return boolean +function is_matching_penalty(penalty) 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. @@ -366,26 +460,47 @@ 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 ~= 0 and - penalty < 10000 and - (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 + penalty = penalty - tex.interlinepenalty + + return penalty ~= 0 and + penalty < INFINITY and ( + 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 + ) +end + +--- Remove the widows and orphans from the page, just after the output routine. +--- +--- This function holds the "meat" of the module. It is called just after the +--- end of the output routine, before the page is shipped out. If the output +--- penalty indicates that the page was broken at a widow or an orphan, we +--- replace one paragraph with the same paragraph, but lengthened by one line. +--- Then, we can push the bottom line of the page to the next page. +--- +--- @param head node +--- @return node +function lwc.remove_widows(head) + local head_save = head -- Save the start of the `head` linked-list + + debug_print("outputpenalty", tex.outputpenalty .. " " .. #paragraphs) + + if not is_matching_penalty(tex.outputpenalty) or + #paragraphs == 0 + then paragraphs = {} return head_save end - info("Widow/orphan detected. Attempting to remove.") + info("Widow/orphan/broken hyphen detected. Attempting to remove") --[[ Find the paragraph on the page with the least cost. @@ -393,10 +508,38 @@ function lwc.remove_widows(head) local paragraph_index = 1 local best_cost = paragraphs[paragraph_index].cost + local last_paragraph + local head_last = last(head) + -- Find the last paragraph on the page, starting at the end, heading in reverse + while head_last do + local value = getattribute(head_last, attribute) + if value then + last_paragraph = value % PAGE_MULTIPLE + break + end + + head_last = head_last.prev + end + + local first_paragraph + -- Find the first paragraph on the page, from the top + local first_attribute_val, first_attribute_head = find_attribute(head, attribute) + if first_attribute_val // 100 == pagenum() - 1 then + -- If the first complete paragraph on the page was initially broken on the + -- previous page, then we can't expand it here so we need to skip it. + first_paragraph = find_attribute( + first_attribute_head.next, + attribute + ) % PAGE_MULTIPLE + else + first_paragraph = first_attribute_val % PAGE_MULTIPLE + end + -- We find the current "best" replacement, then free the unused ones for i, paragraph in pairs(paragraphs) do if paragraph.cost < best_cost and - i ~= last_paragraph + i < last_paragraph and + i >= first_paragraph then -- Clear the old best paragraph flush_list(paragraphs[paragraph_index].node) @@ -421,10 +564,10 @@ function lwc.remove_widows(head) ) if best_cost > tex.getcount(max_cost) or - paragraph_index == last_paragraph + paragraph_index == last_paragraph then -- If the best replacement is too bad, we can't do anything - warning("Widow/Orphan NOT removed on page " .. pagenum()) + warning("Widow/Orphan/broken hyphen NOT removed on page " .. pagenum()) paragraphs = {} return head_save end @@ -434,12 +577,13 @@ function lwc.remove_widows(head) -- Start of final paragraph debug_print("remove_widows", "moving last line") + -- Here we check to see if the widow/orphan was preceded by a large penalty 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 + elseif head.id == penalty_id and head.penalty >= INFINITY then -- Infinite break penalty big_penalty_found = true elseif big_penalty_found and head.id == hlist_id then @@ -451,7 +595,7 @@ function lwc.remove_widows(head) head = last(head_save) break elseif lwc.nobreak_behaviour == "warn" then - warning("Widow/Orphan NOT removed on page " .. pagenum()) + warning("Widow/Orphan/broken hyphen NOT removed on page " .. pagenum()) paragraphs = {} return head_save end @@ -467,6 +611,17 @@ function lwc.remove_widows(head) head = head.prev end + local potential_penalty = head.prev.prev + + if potential_penalty and + potential_penalty.id == penalty_id and + potential_penalty.subtype == linebreakpenalty_subid and + is_matching_penalty(potential_penalty.penalty) + then + warning("Making a new widow/orphan/broken hyphen on page " .. pagenum()) + end + + last_line = copy(head) last(last_line).next = copy(tex.lists[contrib_head]) @@ -489,18 +644,50 @@ function lwc.remove_widows(head) debug_print("remove_widows", "found " .. value) -- Insert the start of the replacement paragraph - if value == paragraph_index + (100 * pagenum()) then + if value == paragraph_index + (PAGE_MULTIPLE * pagenum()) or + value == paragraph_index + (PAGE_MULTIPLE * pagenum()) + SINGLE_LINE + then debug_print("remove_widows", "replacement start") safe_last(target_node) -- Remove any loops + -- Fix the `\\baselineskip` glue between paragraphs + height_difference = ( + next_of_type(head, hlist_id, { subtype = line_subid }).height - + next_of_type(target_node, hlist_id, { subtype = line_subid }).height + ) + + local prev_bls = next_of_type( + head, + glue_id, + { subtype = baselineskip_subid, reverse = true } + ) + + if prev_bls then + prev_bls.width = prev_bls.width + height_difference + end + head.prev.next = target_node free_next_nodes = true end -- Insert the end of the replacement paragraph - if value == -1 * (paragraph_index + (100 * pagenum())) then + if value == -1 * (paragraph_index + (PAGE_MULTIPLE * pagenum())) or + value == paragraph_index + (PAGE_MULTIPLE * pagenum()) + SINGLE_LINE + then debug_print("remove_widows", "replacement end") - safe_last(target_node).next = head.next + local target_node_last = safe_last(target_node) + + if grid_mode_enabled() then + -- Account for the difference in depth + local after_glue = new_node("glue") + after_glue.width = head.depth - target_node_last.depth + target_node_last.next = after_glue + + after_glue.next = head.next + else + target_node_last.next = head.next + end + break end @@ -512,11 +699,10 @@ function lwc.remove_widows(head) end info( - "Widow/orphan successfully removed at paragraph " + "Widow/orphan/broken hyphen successfully removed at paragraph " .. paragraph_index .. " on page " .. pagenum() - .. "." ) paragraphs = {} -- Clear paragraphs array at the end of the page @@ -524,31 +710,90 @@ function lwc.remove_widows(head) return head_save end +--- Create a table of functions to enable or disable a given callback +--- @param t table Parameters of the callback to create +--- callback: string = The \LuaTeX{} callback name +--- func: function = The function to call +--- name: string = The name/ID of the callback +--- category: string = The category for a \ConTeXt{} "Action" +--- position: string = The "position" for a \ConTeXt{} "Action" +--- lowlevel: boolean = If we should use a lowlevel \LuaTeX{} callback instead of a +--- \ConTeXt{} "Action" +--- @return table t Enablers/Disablers for the callback +--- enable: function = Enable the callback +--- disable: function = Disable the callback +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) + end, + disable = function() + luatexbase.remove_from_callback(t.callback, t.name) + end, + } + 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() + nodes.tasks.enableaction(t.category, "lwc." .. t.name) + end, + disable = function() + nodes.tasks.disableaction(t.category, "lwc." .. t.name) + end, + } + 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 + and unfrozen. Because of this, we need to register some callbacks at the + engine level. This is fragile though, because a future \ConTeXt{} update + may decide to register one of these functions, in which case + \lwc/ will crash with a cryptic error message. + ]] + return { + enable = function() callback.register(t.callback, t.func) end, + disable = function() callback.register(t.callback, nil) end, + } + elseif optex then -- Op\TeX{} is very similar to luatexbase + 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 + -- Add all of the callbacks lwc.callbacks = { 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 = register_callback({ callback = "pre_output_filter", - func = lwc.remove_widows, - name = "remove_widows", + func = lwc.remove_widows, + name = "remove_widows", lowlevel = true, }), 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 = register_callback({ callback = "post_linebreak_filter", - func = lwc.mark_paragraphs, - name = "mark_paragraphs", + func = lwc.mark_paragraphs, + name = "mark_paragraphs", category = "finalizers", position = "after", }), @@ -556,6 +801,7 @@ lwc.callbacks = { local enabled = false +--- Enable the paragraph callbacks function lwc.enable_callbacks() debug_print("callbacks", "enabling") if not enabled then @@ -568,6 +814,7 @@ function lwc.enable_callbacks() end end +--- Disable the paragraph callbacks function lwc.disable_callbacks() debug_print("callbacks", "disabling") if enabled then @@ -588,12 +835,88 @@ end function lwc.if_lwc_enabled() debug_print("iflwc") if enabled then - tex.sprint("\\iftrue") + insert_token(iftrue) else - tex.sprint("\\iffalse") + insert_token(iffalse) + end +end + +--- Mangles a macro name so that it's suitable for a specific format +--- @param name string The plain name +--- @param args table<string> The TeX types of the function arguments +--- @return string The mangled name +local function mangle_name(name, args) + if plain then + return "lwc@" .. name:gsub("_", "@") + elseif optex then + return "_lwc_" .. name + elseif context then + return "lwc_" .. name + elseif latex then + return "__lwc_" .. name .. ":" .. string_rep("n", #args) + end +end + +--- Creates a TeX command that evaluates a Lua function +--- @param name string The name of the csname to define +--- @param func function +--- @param args table<string> The TeX types of the function arguments +--- @return nil +local function register_tex_cmd(name, func, args) + local scanning_func + name = mangle_name(name, args) + + if not context then + local scanners = {} + for _, arg in ipairs(args) do + scanners[#scanners+1] = token['scan_' .. arg] + end + + scanning_func = function() + local values = {} + for _, scanner in ipairs(scanners) do + values[#values+1] = scanner() + end + + func(table.unpack(values)) + end + end + + if optex then + define_lua_command(name, scanning_func) + return + elseif plain or latex then + local index = luatexbase.new_luafunction(name) + lua.get_functions_table()[index] = scanning_func + token.set_lua(name, index) + elseif context then + interfaces.implement { + name = name, + public = true, + arguments = args, + actions = func + } end end +register_tex_cmd("if_enabled", lwc.if_lwc_enabled, {}) +register_tex_cmd("enable", lwc.enable_callbacks, {}) +register_tex_cmd("disable", lwc.disable_callbacks, {}) +register_tex_cmd( + "nobreak", + function(str) + lwc.nobreak_behaviour = str + end, + { "string" } +) +register_tex_cmd( + "debug", + function(str) + lwc.debug = str ~= "0" and str ~= "false" and str ~= "stop" + end, + { "string" } +) + --- Silence the luatexbase "Enabling/Removing <callback>" info messages --- --- Every time that a paragraph is typeset, \lwc/ hooks in @@ -612,12 +935,12 @@ end 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) + for i = 1, nups do + local name, func = debug.getupvalue(luatexbase.add_to_callback, i) if name == "luatexbase_log" then debug.setupvalue( luatexbase.add_to_callback, - x, + i, function(text) if text:match("^Inserting") or text:match("^Removing") then return 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 afae1aa6a6..cb6f4c06f9 100644 --- a/macros/luatex/generic/lua-widow-control/lua-widow-control.opm +++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.opm @@ -3,7 +3,7 @@ % SPDX-License-Identifier: MPL-2.0+ % SPDX-FileCopyrightText: 2022 Max Chernoff -\_codedecl\lwcenable{lua-widow-control <v2.0.6>} %%version +\_codedecl\lwcenable{lua-widow-control <v2.1.0>} %%version \_namespace{lwc} \_clubpenalty=1 @@ -19,14 +19,6 @@ \_directlua{require "lua-widow-control"} -% Define \TeX{} wrappers for Lua functions -\_def\.enable{\_directlua{lwc.enable_callbacks()}} -\_def\.disable{\_directlua{lwc.disable_callbacks()}} -\_def\.iflwc{\_directlua{lwc.if_lwc_enabled()}} - -\_let\lwcenable=\.enable -\_let\lwcdisable=\.disable - % Enable \lwc/ by default when the package is loaded. \.enable @@ -37,7 +29,7 @@ \_newcount\.disable_count \_def\.patch_pre{% - \.iflwc% + \.if_enabled% \_advance\.disable_count by 1% \.disable% \_fi% @@ -70,11 +62,18 @@ \_endgroup% \_fi% } -\_let\lwcdisablecmd=\.disable_cmd \.disable_cmd{\_printchap} \.disable_cmd{\_printsec} \.disable_cmd{\_printsecc} +% Make the commands public +\_let\lwcenable=\.enable +\_let\lwcdisable=\.disable +\_let\lwcdisablecmd=\.disable_cmd +\_let\lwcdebug=\.debug +\_let\iflwc=\.if_enabled +\_let\lwcnobreak=\.nobreak + \_endnamespace \_endcode diff --git a/macros/luatex/generic/lua-widow-control/lua-widow-control.pdf b/macros/luatex/generic/lua-widow-control/lua-widow-control.pdf Binary files differindex 3ab6e51886..709042c553 100644 --- a/macros/luatex/generic/lua-widow-control/lua-widow-control.pdf +++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.pdf 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 a44b5af1da..e7179e6a9e 100644 --- a/macros/luatex/generic/lua-widow-control/lua-widow-control.sty +++ b/macros/luatex/generic/lua-widow-control/lua-widow-control.sty @@ -13,7 +13,7 @@ \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.6}{2022-04-23} %%version %%dashdate +\DeclareCurrentRelease{v2.1.0}{2022-05-14} %%version %%dashdate % If this version of LaTeX doesn't support command hooks, then we load % the last v1.1.X version of the package. @@ -23,8 +23,8 @@ \ProvidesExplPackage {lua-widow-control} - {2022/04/23} %%slashdate - {v2.0.6} %%version + {2022/05/14} %%dashdate + {v2.1.0} %%version {Use Lua to remove widows and orphans} % Unconditional Package Loads @@ -105,6 +105,7 @@ disablecmds .clist_gset:N = \g__lwc_disablecmds_cl, disablecmds .value_required:n = false, disablecmds .initial:n = { \@sect, % LaTeX default + \@ssect, % LaTeX starred \M@sect, % Memoir \@mem@old@ssect, % Memoir Starred \ttl@straight@ii, % titlesec normal @@ -112,11 +113,6 @@ \ttl@part@ii, % titlesec part }, 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 @@ -138,16 +134,10 @@ } % Core Function Definitions -\cs_new:Npn \__lwc_enable: { - \lua_now:n { lwc.enable_callbacks() } -} +\cs_new_eq:NN \iflwc \__lwc_iflwc: -\cs_new:Npn \__lwc_disable: { - \lua_now:n { lwc.disable_callbacks() } -} - -\prg_set_conditional:Nnn \__lwc_if_enabled: { T, F, TF } { - \lua_now:n { lwc.if_lwc_enabled() } +\prg_new_conditional:Nnn \__lwc_if_enabled: { T, F, TF } { + \__lwc_if_enabled: \prg_return_true: \else \prg_return_false: @@ -230,8 +220,12 @@ 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 }, + debug / true .code:n = \__lwc_debug:n { true }, + debug / false .code:n = \__lwc_debug:n { false }, + + nobreak .code:n = \__lwc_nobreak:n { #1 }, + nobreak .value_required:n = true, + nobreak .initial:n = keep, strict .meta:n = { emergencystretch = 0pt, max-cost = 5000, 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 b667d05c11..78385ad698 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 v2.0.6} %%version +\wlog{lua-widow-control v2.1.0} %%version \ifx\directlua\undefined \errmessage{% @@ -35,13 +35,8 @@ \expandglyphsinfont\the\font 20 20 5 \adjustspacing=2 -% Define \TeX{} wrappers for Lua functions -\def\lwcenable{\directlua{lwc.enable_callbacks()}} -\def\lwcdisable{\directlua{lwc.disable_callbacks()}} -\def\iflwc{\directlua{lwc.if_lwc_enabled()}} - % Enable \lwc/ by default when the package is loaded. -\lwcenable +\lwc@enable % Expansion of some parts of the document, such as section headings, is quite % undesirable, so we'll disable \lwc/ for certain commands. @@ -50,15 +45,15 @@ \newcount\lwc@disable@count \def\lwc@patch@pre{% - \iflwc% + \lwc@if@enabled% \advance\lwc@disable@count by 1% - \lwcdisable% + \lwc@disable% \fi% } \def\lwc@patch@post{ \ifnum\lwc@disable@count>0% - \lwcenable% + \lwc@enable% \advance\lwc@disable@count by -1% \fi } @@ -89,6 +84,13 @@ \lwcdisablecmd{\beginsection} % Sectioning \endgroup +% Make the commands public +\let\lwcenable=\lwc@enable +\let\lwcdisable=\lwc@disable +\let\lwcdebug=\lwc@debug +\let\iflwc=\lwc@if@enabled +\let\lwcnobreak=\lwc@nobreak + \catcode`@=12 \endinput diff --git a/macros/luatex/generic/lua-widow-control/lwc-documentation.tex b/macros/luatex/generic/lua-widow-control/lwc-documentation.tex index 7f05a344f0..23b13c0250 100644 --- a/macros/luatex/generic/lua-widow-control/lwc-documentation.tex +++ b/macros/luatex/generic/lua-widow-control/lwc-documentation.tex @@ -5,7 +5,10 @@ % SPDX-License-Identifier: MPL-2.0+ OR CC-BY-SA-4.0+ % SPDX-FileCopyrightText: 2022 Max Chernoff -% Warning: Compiles slowly +\doifnot{\contextmark}{LMTX}{ + \errhelp{LMTX/MkXL is required to compile this file.} + \errmessage{Fatal error, exiting.} +} \environment lwc-documentation @@ -37,7 +40,7 @@ \startdocument[ title=lua-widow-control, author=Max Chernoff, - version=2.0.6, %%version + version=2.1.0, %%version github=https://github.com/gucci-on-fleek/lua-widow-control, ctan=https://www.ctan.org/pkg/lua-widow-control, ] @@ -316,7 +319,7 @@ Note that the \LaTeX{} option does not \emph{append} commands to the disable lis \startTABLE \NC \plainop/ - \NC \inlineTEX{\lwcdisablecmd{$\meta{\backslash macro}$}} + \NC \inlineTEX{\lwcdisablecmd $\meta{\backslash macro}$} \NC\NR \NC \LaTeX{} \NC \inlineTEX{\lwcsetup{disablecmds = {$\meta{macronameone}$, $\meta{macronametwo}$}}} @@ -363,7 +366,7 @@ When \lwc/ encounters an orphan, it removes it by removing the orphaned line to \startTABLE \NC \plainop/ - \NC\inlineTEX{\directlua{lwc.nobreak_behaviour = "$\meta{value}$"}} + \NC\inlineTEX{\lwcnobreak{$\meta{value}$}} \NC\NR \NC \LaTeX{} \NC\inlineTEX{\lwcsetup{nobreak = $\meta{value}$}} @@ -409,8 +412,8 @@ Very advanced users may also set a custom cost function by redefining the \inlin \startTABLE \NC \plainop/ - \NC\inlineTEX{\directlua{lwc.debug = true}} - \NC\NR\NC\NC\inlineTEX{\directlua{lwc.debug = false}} + \NC\inlineTEX{\lwcdebug 1} + \NC\NR\NC\NC\inlineTEX{\lwcdebug 0} \NC\NR \NC \LaTeX{} \NC\inlineTEX{\lwcsetup{debug = true}} 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 1e52f8d901..090ff5aa80 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=2.0.6, %%version +%D version=2.1.0, %%version %D title=lua-widow-control, %D subtitle=\ConTeXt module for lua-widow-control, %D author=Max Chernoff, -%D date=2022-04-23, %%dashdate +%D date=2022-05-14, %%dashdate %D copyright=Max Chernoff, %D license=MPL-2.0+, %D url=https://github.com/gucci-on-fleek/lua-widow-control] @@ -16,39 +16,36 @@ \installcommandhandler \????lwc {lwc} \????lwc \newdimen\lwc_emergency_stretch +\newcount\lwc_max_cost \appendtoks \lwc_emergency_stretch=\lwcparameter{emergencystretch} -\to\everysetuplwc -\appendtoks \doifelse{\lwcparameter{\c!state}}\v!start{ - \ctxlua{lwc.enable_callbacks()} + \lwc_enable }{ - \ctxlua{lwc.disable_callbacks()} + \lwc_disable } -\to\everysetuplwc -\appendtoks - \doifelse{\lwcparameter{debug}}\v!start{ - \ctxlua{lwc.debug = true} - }{ - \ctxlua{lwc.debug = false} - } -\to\everysetuplwc + \lwc_debug{\lwcparameter{debug}} -\appendtoks - \ctxlua{lwc.nobreak_behaviour = "\lwcparameter{nobreak}"} -\to\everysetuplwc + \lwc_nobreak{\lwcparameter{nobreak}} -\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] + \directsetup{*reset} + + \clubpenalty=\lwcparameter{orphanpenalty} + \widowpenalty=\lwcparameter{widowpenalty} + \displaywidowpenalty=\lwcparameter{widowpenalty} + \brokenpenalty=\lwcparameter{brokenpenalty} + \stopsetups + + \startsetups[grid][*default] + \directsetup{*reset} + \clubpenalty=\lwcparameter{orphanpenalty} \widowpenalty=\lwcparameter{widowpenalty} \displaywidowpenalty=\lwcparameter{widowpenalty} @@ -58,8 +55,6 @@ \setups[*default] \to\everysetuplwc -\define\iflwc{\ctxlua{lwc.if_lwc_enabled()}} - \ctxloadluafile{lua-widow-control} \setuplwc[ @@ -86,14 +81,14 @@ \newcount\lwc_disable_count \define\lwc_patch_pre{% - \iflwc% + \lwc_if_enabled% \advance\lwc_disable_count by 1% \setuplwc[\c!state=\v!stop]% \fi% } \define\lwc_patch_post{ - \ifnum\lwc_disable_count>0% + \ifnum\lwc_disable_count>0\relax% \setuplwc[\c!state=\v!start]% \advance\lwc_disable_count by -1% \fi% @@ -102,5 +97,8 @@ \prependtoks\lwc_patch_pre\to\everybeforesectionheadhandle % Sectioning \prependtoks\lwc_patch_post\to\everyaftersectionheadhandle +% Make the commands public +\let\iflwc=\lwc_if_enabled + \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 1e52f8d901..090ff5aa80 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=2.0.6, %%version +%D version=2.1.0, %%version %D title=lua-widow-control, %D subtitle=\ConTeXt module for lua-widow-control, %D author=Max Chernoff, -%D date=2022-04-23, %%dashdate +%D date=2022-05-14, %%dashdate %D copyright=Max Chernoff, %D license=MPL-2.0+, %D url=https://github.com/gucci-on-fleek/lua-widow-control] @@ -16,39 +16,36 @@ \installcommandhandler \????lwc {lwc} \????lwc \newdimen\lwc_emergency_stretch +\newcount\lwc_max_cost \appendtoks \lwc_emergency_stretch=\lwcparameter{emergencystretch} -\to\everysetuplwc -\appendtoks \doifelse{\lwcparameter{\c!state}}\v!start{ - \ctxlua{lwc.enable_callbacks()} + \lwc_enable }{ - \ctxlua{lwc.disable_callbacks()} + \lwc_disable } -\to\everysetuplwc -\appendtoks - \doifelse{\lwcparameter{debug}}\v!start{ - \ctxlua{lwc.debug = true} - }{ - \ctxlua{lwc.debug = false} - } -\to\everysetuplwc + \lwc_debug{\lwcparameter{debug}} -\appendtoks - \ctxlua{lwc.nobreak_behaviour = "\lwcparameter{nobreak}"} -\to\everysetuplwc + \lwc_nobreak{\lwcparameter{nobreak}} -\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] + \directsetup{*reset} + + \clubpenalty=\lwcparameter{orphanpenalty} + \widowpenalty=\lwcparameter{widowpenalty} + \displaywidowpenalty=\lwcparameter{widowpenalty} + \brokenpenalty=\lwcparameter{brokenpenalty} + \stopsetups + + \startsetups[grid][*default] + \directsetup{*reset} + \clubpenalty=\lwcparameter{orphanpenalty} \widowpenalty=\lwcparameter{widowpenalty} \displaywidowpenalty=\lwcparameter{widowpenalty} @@ -58,8 +55,6 @@ \setups[*default] \to\everysetuplwc -\define\iflwc{\ctxlua{lwc.if_lwc_enabled()}} - \ctxloadluafile{lua-widow-control} \setuplwc[ @@ -86,14 +81,14 @@ \newcount\lwc_disable_count \define\lwc_patch_pre{% - \iflwc% + \lwc_if_enabled% \advance\lwc_disable_count by 1% \setuplwc[\c!state=\v!stop]% \fi% } \define\lwc_patch_post{ - \ifnum\lwc_disable_count>0% + \ifnum\lwc_disable_count>0\relax% \setuplwc[\c!state=\v!start]% \advance\lwc_disable_count by -1% \fi% @@ -102,5 +97,8 @@ \prependtoks\lwc_patch_pre\to\everybeforesectionheadhandle % Sectioning \prependtoks\lwc_patch_post\to\everyaftersectionheadhandle +% Make the commands public +\let\iflwc=\lwc_if_enabled + \protect \stopmodule |