diff options
author | Karl Berry <karl@freefriends.org> | 2022-06-18 20:06:28 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2022-06-18 20:06:28 +0000 |
commit | 6aef67d9f5fa3aa0eff4adc1196a7cf4e36c81a9 (patch) | |
tree | f96c841c384afd9888443815fff064c92cedea40 /Master/texmf-dist/tex | |
parent | ebf61fc815a3b70fb28f94ecffbcc7243dcf833c (diff) |
lua-widow-control (18jun22)
git-svn-id: svn://tug.org/texlive/trunk@63642 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex')
6 files changed, 257 insertions, 61 deletions
diff --git a/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkxl b/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkxl index 682e8d23c5d..59825332020 100644 --- a/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkxl +++ b/Master/texmf-dist/tex/context/third/lua-widow-control/t-lua-widow-control.mkxl @@ -1,10 +1,10 @@ %D \module %D [ file=t-lua-widow-control, -%D version=2.1.2, %%version +%D version=2.2.0, %%version %D title=lua-widow-control, %D subtitle=\ConTeXt module for lua-widow-control, %D author=Max Chernoff, -%D date=2022-05-26, %%dashdate +%D date=2022-06-17, %%dashdate %D copyright=Max Chernoff, %D license=MPL-2.0+, %D url=https://github.com/gucci-on-fleek/lua-widow-control] @@ -28,6 +28,8 @@ \lwc_debug{\lwcparameter{debug}} + \lwc_draft{\lwcparameter{draft}} + \lwc_nobreak{\lwcparameter{nobreak}} \lwc_max_cost=\lwcparameter{maxcost} @@ -61,6 +63,7 @@ emergencystretch=3em, \c!state=\v!start, debug=\v!stop, + draft=\v!stop, orphanpenalty=1, widowpenalty=1, brokenpenalty=1, diff --git a/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control-2022-02-22.sty b/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control-2022-02-22.sty index b7b2ab44c04..74645635600 100644 --- a/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control-2022-02-22.sty +++ b/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control-2022-02-22.sty @@ -12,8 +12,8 @@ % report a real version number here for debugging. \PackageInfo{lua-widow-control}{% Real version: - 2022/05/26 %%slashdate - v2.1.2 %%version + 2022/06/17 %%slashdate + v2.2.0 %%version } \PackageWarning{lua-widow-control}{% diff --git a/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control.sty b/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control.sty index 89f417a4576..7d1be850b91 100644 --- a/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control.sty +++ b/Master/texmf-dist/tex/lualatex/lua-widow-control/lua-widow-control.sty @@ -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.1.2}{2022-05-26} %%version %%dashdate +\DeclareCurrentRelease{v2.2.0}{2022-06-17} %%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/05/26} %%slashdate - {v2.1.2} %%version + {2022/06/17} %%slashdate + {v2.2.0} %%version {Use Lua to remove widows and orphans} % Unconditional Package Loads @@ -116,7 +116,11 @@ } % Load the Lua code -\lua_now:n { require "lua-widow-control" } +\cs_if_exist:NTF \lua_load_module:n { + \lua_load_module:n { lua-widow-control } +} { + \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 @@ -220,8 +224,16 @@ disable .value_forbidden:n = true, debug .choice:, - debug / true .code:n = \__lwc_debug:n { true }, - debug / false .code:n = \__lwc_debug:n { false }, + debug / true .code:n = \__lwc_debug:n { true }, + debug / false .code:n = \__lwc_debug:n { false }, + debug .default:n = true, + debug .value_required:n = false, + + draft .choice:, + draft / true .code:n = \__lwc_draft:n { true }, + draft / false .code:n = \__lwc_draft:n { false }, + draft .default:n = true, + draft .value_required:n = false, nobreak .code:n = \__lwc_nobreak:n { #1 }, nobreak .value_required:n = true, diff --git a/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.lua b/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.lua index 4bd109e4839..800faf56242 100644 --- a/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.lua +++ b/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.lua @@ -118,10 +118,17 @@ local iftrue = token.create("iftrue") local INFINITY = 10000 local INSERT_CLASS_MULTIPLE = 1000 * 1000 local INSERT_FIRST_MULTIPLE = 1000 +local llap_offset = math.max(tex.dimen.parindent, tex.sp("12pt")) local min_col_width = tex.sp("250pt") local PAGE_MULTIPLE = 100 local SINGLE_LINE = 50 +lwc.colours = { + expanded = {0.00, 0.70, 0.25}, + failure = {0.90, 0.00, 0.25}, + moved = {0.25, 0.25, 1.00}, +} + --[[ Package/module initialization. Here, we replace any format/engine-specific variables/functions with some @@ -135,6 +142,7 @@ local contrib_head, max_cost, pagenum, paragraph_attribute, + shrink_order, stretch_order, warning @@ -142,9 +150,11 @@ if lmtx then -- LMTX has removed underscores from most of the Lua parts debug("LMTX") contrib_head = "contributehead" + shrink_order = "shrinkorder" stretch_order = "stretchorder" else contrib_head = "contrib_head" + shrink_order = "shrink_order" stretch_order = "stretch_order" end @@ -184,8 +194,8 @@ elseif plain or latex or optex then debug("Plain/LaTeX") luatexbase.provides_module { name = lwc.name, - date = "2022/05/26", --%%slashdate - version = "2.1.2", --%%version + date = "2022/06/17", --%%slashdate + version = "2.2.0", --%%version description = [[ This module provides a LuaTeX-based solution to prevent @@ -211,6 +221,23 @@ else -- This shouldn't ever happen Please use LaTeX, Plain TeX, ConTeXt or OpTeX.]] end +--[[ Select the fonts + + We want to use cmr7 for the draft mode cost displays, and the easiest + way to do so is to just hardcode the font id's. This relies on some + implementation details; however, it is very unlikely to ever be an issue + ]] +local SMALL_FONT +if plain then + SMALL_FONT = 4 +elseif latex then + SMALL_FONT = 7 +elseif optex then + SMALL_FONT = 7 +elseif context then + SMALL_FONT = 3 +end + --[[ Table to hold the alternate paragraph versions. This is global(ish) mutable state, which isn't ideal, but any other way of @@ -219,8 +246,7 @@ end local paragraphs = {} local inserts = {} ---[[ - Function definitions +--[[ Function definitions ]] --- Gets the current paragraph and page locations @@ -268,6 +294,7 @@ 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` @@ -371,6 +398,91 @@ local function natural_paragraph(head) end +lwc.draft_mode = false +--- Changes the text colour in a node list if draft mode is active +--- +--- @param head node The first node to colour +--- @param colour table<number> A 3-tuple of RGB values +--- @return node head The coloured node +local function colour_list(head, colour) + if not lwc.draft_mode then + return head + end + + -- Adapted from https://tex.stackexchange.com/a/372437 + -- \\pdfextension colorstack is ignored in LMTX + local start_colour = new_node("whatsit", "pdf_colorstack") + start_colour.stack = 0 + start_colour.command = 1 + start_colour.data = string.format("%.2f %.2f %.2f rg", table.unpack(colour)) + + local end_colour = new_node("whatsit", "pdf_colorstack") + end_colour.stack = 0 + end_colour.command = 2 + + start_colour.next = head + last(head).next = end_colour + + return start_colour +end + + +--- Generate an \\llap'ed box containing the provided string +--- +--- @param str string The string to typeset +--- @return node head The box node +local function llap_string(str) + local first = new_node("glue") + first.width = llap_offset + + local m = first + for letter in str:gmatch(".") do + local n = new_node("glyph") + n.font = SMALL_FONT + n.char = string.byte(letter) + + m.next = n + m = n + end + + local hss = new_node("glue") + hss.stretch = 1 + hss[stretch_order] = 1 + hss.shrink = 1 + hss[shrink_order] = 1 + m.next = hss + + return node.hpack(first, 0, "exactly") +end + + +--- Typesets the cost of a paragraph beside it in draft mode +--- +--- @param paragraph node +--- @param cost number +--- @return nil +local function show_cost(paragraph, cost) + if not lwc.draft_mode then + return + end + + local last_hlist_end = last(next_of_type( + last(paragraph), + hlist_id, + { subtype = line_subid, reverse = true } + ).list) + + local cost_str + if cost < math.maxinteger then + cost_str = string.format("%.0f", cost) + else + cost_str = "infinite" + end + + last_hlist_end.next = llap_string(cost_str) +end + + --- Saves each paragraph, but lengthened by 1 line --- --- Called by the `pre_linebreak_filter` callback @@ -411,15 +523,27 @@ function lwc.save_paragraphs(head) local long_cost = lwc.paragraph_cost(long_info.demerits, long_info.prevgraf) - if long_info.prevgraf == natural_info.prevgraf + 1 and - long_cost > 10 -- Any paragraph that is "free" to expand is suspicious + if long_info.prevgraf ~= natural_info.prevgraf + 1 or + long_cost < 10 -- Any paragraph that is "free" to expand is suspicious then - table.insert(paragraphs, { - cost = long_cost, - node = next_of_type(long_node, hlist_id, { subtype = line_subid }) - }) + -- This paragraph is infinitely bad + long_cost = math.maxinteger end + local saved_node = next_of_type(long_node, hlist_id, { subtype = line_subid }) + + show_cost(saved_node, long_cost) + for n in traverse_id(hlist_id, saved_node) do + n.list = colour_list(n.list, lwc.colours.expanded) + end + + table.insert(paragraphs, { + cost = long_cost, + node = copy_list(saved_node) + }) + + free_list(long_node) + -- Print some debugging information get_chars(head) debug(get_location(), "nat lines " .. natural_info.prevgraf) @@ -452,21 +576,21 @@ local function mark_paragraphs(head) -- Tag the 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) + local top = next_of_type(head, hlist_id, { subtype = line_subid }) + local bottom = last(head) - while bottom_para.id == insert_id do - bottom_para = bottom_para.prev + while bottom.id == insert_id do + bottom = bottom.prev end - if top_para ~= bottom_para then + if top ~= bottom then set_attribute( - top_para, + top, paragraph_attribute, #paragraphs + (PAGE_MULTIPLE * pagenum()) ) set_attribute( - bottom_para, + bottom, paragraph_attribute, -1 * (#paragraphs + (PAGE_MULTIPLE * pagenum())) ) @@ -474,11 +598,15 @@ local function mark_paragraphs(head) -- We need a special tag for a 1-line paragraph since the node can only -- have a single attribute value set_attribute( - top_para, + top, paragraph_attribute, #paragraphs + (PAGE_MULTIPLE * pagenum()) + SINGLE_LINE ) end + + if #paragraphs > 0 then + show_cost(head, paragraphs[#paragraphs].cost) + end end end @@ -582,12 +710,14 @@ end --- --- @return nil local function reset_state() + for _, paragraph in ipairs(paragraphs) do + free_list(paragraph.node) + end paragraphs = {} for _, insert in ipairs(inserts) do free(insert) end - inserts = {} end @@ -597,6 +727,25 @@ end --- @return nil local function remove_widows_fail() warning("Widow/Orphan/broken hyphen NOT removed on page " .. pagenum()) + + local last_line = next_of_type( + last(tex_lists.page_head), + hlist_id, + { subtype = line_subid, reverse = true } + ) + if last_line then + last_line.list = colour_list(last_line.list, lwc.colours.failure) + end + + local next_first_line = next_of_type( + tex_lists[contrib_head], + hlist_id, + { subtype = line_subid } + ) + if next_first_line then + next_first_line.list = colour_list(next_first_line.list, lwc.colours.failure) + end + reset_state() end @@ -625,18 +774,24 @@ local function first_last_paragraphs(head) -- Find the first paragraph on the page, from the top local first_val, first_head = find_attribute(head, paragraph_attribute) - if first_val // PAGE_MULTIPLE == pagenum() - 1 then + while abs(first_val) // PAGE_MULTIPLE == pagenum() - 1 do --[[ 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_index = find_attribute( + first_val, first_head = find_attribute( first_head.next, paragraph_attribute - ) % PAGE_MULTIPLE - else - first_index = first_val % PAGE_MULTIPLE + ) end + first_index = first_val % PAGE_MULTIPLE + + if first_index >= SINGLE_LINE then + first_index = first_index - SINGLE_LINE + end + + debug("first/last", first_index .. "/" .. last_index) + return first_index, last_index end @@ -653,21 +808,13 @@ local function best_paragraph(head) local best_index = 1 local best_cost = paragraphs[best_index].cost - -- We find the current "best" replacement, then free the unused ones + -- We find the current "best" replacement for index, paragraph in pairs(paragraphs) do if paragraph.cost < best_cost and index < last_paragraph_index and index >= first_paragraph_index then - -- Free the old best paragraph - free_list(paragraphs[best_index].node) - paragraphs[best_index].node = nil - -- Set the new best paragraph best_index, best_cost = index, paragraph.cost - elseif index > 1 then - -- Not sure why `i > 1` is required? - free_list(paragraph.node) - paragraph.node = nil end end @@ -677,7 +824,8 @@ local function best_paragraph(head) ) if best_cost > tex_count[max_cost] or - best_index == last_paragraph_index + best_index == last_paragraph_index or + best_cost == math.maxinteger then return nil else @@ -737,9 +885,11 @@ local function get_inserts(last_line) if box_value > 0 then selected_inserts[#selected_inserts + 1] = copy(inserts[box_value]) end - end - m = m.next + m = free(m) + else + m = m.next + end end if not insert_box.list then @@ -811,10 +961,19 @@ local function move_last_line(head) is_matching_penalty(potential_penalty.penalty) then warning("Making a new widow/orphan/broken hyphen on page " .. pagenum()) + + local second_last_line = next_of_type( + potential_penalty, + hlist_id, + { subtype = line_subid, reverse = true } + ) + second_last_line.list = colour_list(second_last_line.list, lwc.colours.failure) end last_line = copy_list(n) + last_line.list = colour_list(last_line.list, lwc.colours.moved) + -- Reinsert any inserts originally present in this moved line local selected_inserts = get_inserts(last_line) for _, insert in ipairs(selected_inserts) do @@ -824,10 +983,11 @@ local function move_last_line(head) -- Add back in the content from the next page last(last_line).next = copy_list(tex_lists[contrib_head]) + free_list(n.prev.prev.next) n.prev.prev.next = nil -- Set the content of the next page - last(last_line) + free_list(tex_lists[contrib_head]) tex_lists[contrib_head] = last_line return true @@ -842,11 +1002,11 @@ end --- @param head node --- @param paragraph_index number local function replace_paragraph(head, paragraph_index) - local target_node = paragraphs[paragraph_index].node - local free_next_nodes = false + local target_node = copy_list(paragraphs[paragraph_index].node) local start_found = false local end_found = false + local free_nodes_begin -- Loop through all of the nodes on the page with the lwc attribute local n = head @@ -884,7 +1044,7 @@ local function replace_paragraph(head, paragraph_index) end n.prev.next = target_node - free_next_nodes = true + free_nodes_begin = n end -- Insert the end of the replacement paragraph @@ -907,17 +1067,17 @@ local function replace_paragraph(head, paragraph_index) target_node_last.next = n.next end + n.next = nil + break end - if free_next_nodes then - n = free(n) - else - n = n.next - end + n = n.next end - if not (start_found and end_found) then + if start_found and end_found then + free_list(free_nodes_begin) + else warning("Paragraph NOT expanded on page " .. pagenum()) end end @@ -946,24 +1106,30 @@ function lwc.remove_widows(head) -- Nothing that we can do if there aren't any paragraphs available to expand if #paragraphs == 0 then + debug("failure", "no paragraphs to expand") remove_widows_fail() return head end -- Check the original height of \\box255 local vsize = tex_dimen.vsize - local orig_height_diff = vpack(head).height - vsize + local orig_vpack = vpack(head) + local orig_height_diff = orig_vpack.height - vsize + orig_vpack.list = nil + free(orig_vpack) -- Find the paragraph to expand local paragraph_index = best_paragraph(head) if not paragraph_index then + debug("failure", "no good paragraph") remove_widows_fail() return head end -- Move the last line of the page to the next page if not move_last_line(head) then + debug("failure", "can't move last line") remove_widows_fail() return head end @@ -975,13 +1141,19 @@ function lwc.remove_widows(head) over/underfull box warnings, so we correct any discrepancies here. ]] - local new_height_diff = vpack(head).height - vsize + local new_vpack = vpack(head) + local new_height_diff = new_vpack.height - vsize + new_vpack.list = nil + free(new_vpack) -- We need the original height discrepancy in case there are \\vfill's local net_height_diff = orig_height_diff - new_height_diff + local bls = tex.skip.baselineskip + local bls_width = bls.width + free(bls) if abs(net_height_diff) > 0 and -- A difference larger than 0.25\\baselineskip is probably not from \lwc/ - abs(net_height_diff) < tex.skip.baselineskip.width / 4 + abs(net_height_diff) < bls_width / 4 then local bottom_glue = new_node("glue") bottom_glue.width = net_height_diff @@ -1214,6 +1386,13 @@ register_tex_cmd( end, { "string" } ) +register_tex_cmd( + "draft", + function(str) + lwc.draft_mode = str ~= "0" and str ~= "false" and str ~= "stop" + end, + { "string" } +) --- Silence the luatexbase "Enabling/Removing <callback>" info messages --- diff --git a/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.tex b/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.tex index b8ae064c044..2e1dcef902e 100644 --- a/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.tex +++ b/Master/texmf-dist/tex/luatex/lua-widow-control/lua-widow-control.tex @@ -3,7 +3,7 @@ % SPDX-License-Identifier: MPL-2.0+ % SPDX-FileCopyrightText: 2022 Max Chernoff -\wlog{lua-widow-control v2.1.2} %%version +\wlog{lua-widow-control v2.2.0} %%version \ifx\directlua\undefined \errmessage{% @@ -88,6 +88,7 @@ \let\lwcenable=\lwc@enable \let\lwcdisable=\lwc@disable \let\lwcdebug=\lwc@debug +\let\lwcdraft=\lwc@draft \let\iflwc=\lwc@if@enabled \let\lwcnobreak=\lwc@nobreak diff --git a/Master/texmf-dist/tex/optex/lua-widow-control/lua-widow-control.opm b/Master/texmf-dist/tex/optex/lua-widow-control/lua-widow-control.opm index f1261e6c249..ff26f856c5f 100644 --- a/Master/texmf-dist/tex/optex/lua-widow-control/lua-widow-control.opm +++ b/Master/texmf-dist/tex/optex/lua-widow-control/lua-widow-control.opm @@ -3,7 +3,7 @@ % SPDX-License-Identifier: MPL-2.0+ % SPDX-FileCopyrightText: 2022 Max Chernoff -\_codedecl\lwcenable{lua-widow-control <v2.1.2>} %%version +\_codedecl\lwcenable{lua-widow-control <v2.2.0>} %%version \_namespace{lwc} \_clubpenalty=1 @@ -72,6 +72,7 @@ \_let\lwcdisable=\.disable \_let\lwcdisablecmd=\.disable_cmd \_let\lwcdebug=\.debug +\_let\lwcdraft=\.draft \_let\iflwc=\.if_enabled \_let\lwcnobreak=\.nobreak |