texlive[54690] Master/texmf-dist: lua-ul (1apr20)

commits+karl at tug.org commits+karl at tug.org
Sun Apr 12 01:00:41 CEST 2020


Revision: 54690
          http://tug.org/svn/texlive?view=revision&revision=54690
Author:   karl
Date:     2020-04-12 01:00:41 +0200 (Sun, 12 Apr 2020)
Log Message:
-----------
lua-ul (1apr20)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/lualatex/lua-ul/lua-ul.pdf
    trunk/Master/texmf-dist/source/lualatex/lua-ul/lua-ul.dtx
    trunk/Master/texmf-dist/tex/lualatex/lua-ul/lua-ul.lua
    trunk/Master/texmf-dist/tex/lualatex/lua-ul/lua-ul.sty
    trunk/Master/texmf-dist/tex/lualatex/lua-ul/pre_append_to_vlist_filter.lua

Modified: trunk/Master/texmf-dist/doc/lualatex/lua-ul/lua-ul.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/source/lualatex/lua-ul/lua-ul.dtx
===================================================================
--- trunk/Master/texmf-dist/source/lualatex/lua-ul/lua-ul.dtx	2020-04-11 23:00:22 UTC (rev 54689)
+++ trunk/Master/texmf-dist/source/lualatex/lua-ul/lua-ul.dtx	2020-04-11 23:00:41 UTC (rev 54690)
@@ -168,9 +168,8 @@
 %
 % The final argument contains the actual leader command. You should omit the final glue normally passed to \verb+\leaders+,
 % so e.g.\ write \verb+\leaders\hbox{ . }+ without appending \verb+\hfill+ or \verb+\hskip1pt+ etc.
-% The height and depth of your leaders is ignored to ensure that adding underlines etc. does not change the general text layout.
-% It is your responsibility to ensure that you are not too high or too low and intercept other lines. On the other hand,
-% running dimensions work fine if you use a rule.
+% In most cases, the height and depth of your underlines is passed on to TeX to ensure that a deep underline does not interept other lines.
+% On the other hand, running dimensions work fine if you use a rule.
 %
 % For example, the special underline commands demonstrated above are implemented as
 %
@@ -259,7 +258,7 @@
     current = box
   end
   return call_callback("append_to_vlist_filter",
-                       box, locationcode, prevdepth, mirrored)
+                       current, locationcode, prevdepth, mirrored)
 end
 
 callback_define('append_to_vlist_filter',
@@ -287,15 +286,75 @@
 %<*luacode>
 % \fi
 %    \begin{macrocode}
+local unset_t = node.id'unset'
 local hlist_t = node.id'hlist'
 local vlist_t = node.id'vlist'
 local kern_t = node.id'kern'
 local glue_t = node.id'glue'
 
+local properties = node.direct.get_properties_table()
+
+local has_attribute = node.direct.has_attribute
+local set_attribute = node.direct.set_attribute
+local dimensions = node.direct.dimensions
+local flush_node = node.direct.flush_node
+local getboth = node.direct.getboth
+local getfield = node.direct.getfield
+local getglue = node.direct.getglue
+local getleader = node.direct.getleader
+local getlist = node.direct.getlist
+local setheight = node.direct.setheight
+local setdepth = node.direct.setdepth
+local getheight = node.direct.getheight
+local getdepth = node.direct.getdepth
+local getnext = node.direct.getnext
+local getshift = node.direct.getshift
+local insert_after = node.direct.insert_after
+local insert_before = node.direct.insert_before
+local nodecopy = node.direct.copy
+local nodenew = node.direct.new
+local setboth = node.direct.setboth
+local setlink = node.direct.setlink
+local hpack = node.direct.hpack
+local setfield = node.direct.setfield
+local slide = node.direct.slide
+local setglue = node.direct.setglue
+local setnext = node.direct.setnext
+local setshift = node.direct.setshift
+local todirect = node.direct.todirect
+local tonode = node.direct.tonode
+local traverse = node.direct.traverse
+local traverse_id = node.direct.traverse_id
+local traverse_list = node.direct.traverse_list
+% The following two are needed to deal with unset nodes
+local getList = function(n) return getfield(n, 'list') end
+local setList = function(n, h) return setfield(n, 'list', h) end
+
+local tokennew = token.new
+local set_lua = token.set_lua
+local scan_keyword = token.scan_keyword
+local scan_list = token.scan_list
+local scan_int = token.scan_int
+local put_next = token.put_next
+local texerror = tex.error
+
 local char_given = token.command_id'char_given'
 
 local underlineattrs = {}
 local underline_types = {}
+local underline_strict_flag = {}
+local underline_over_flag = {}
+
+local vmode do
+  for k, v in pairs(tex.getmodevalues()) do
+  if v == "vertical" then
+      vmode = k
+      break
+    end
+  end
+end
+local texnest = tex.nest
+
 local saved_values = {}
 local function new_underline_type()
   for i=1,#underlineattrs do
@@ -303,30 +362,58 @@
     saved_values[i] = tex.attribute[attr]
     tex.attribute[attr] = -0x7FFFFFFF
   end
-  local b = token.scan_list()
+  local strict_flag = scan_keyword'strict'
+  local over_flag = scan_keyword'over'
+  local b = todirect(scan_list())
   for i=1,#underlineattrs do
     tex.attribute[underlineattrs[i]] = saved_values[i]
   end
-  local lead = b.head
-  if not lead.leader then
-    tex.error("Leader required", {"An underline type has to \z
+  local lead = getlist(b)
+  if not getleader(lead) then
+    texerror("Leader required", {"An underline type has to \z
       be defined by leader. You should use one of the", "commands \z
       \\leaders, \\cleaders, or \\xleader, or \\gleaders here."})
   else
-    if lead.next then
-    tex.error("Too many nodes", {"An underline type can only be \z
-        defined by a single leaders specification,", "not by \z
-        multiple nodes. Maybe you supplied an additional glue?",
-        "Anyway, the additional nodes will be ignored"})
+    local after = getnext(lead)
+    if after then
+      texerror("Too many nodes", {"An underline type can only be \z
+          defined by a single leaders specification,", "not by \z
+          multiple nodes. Maybe you supplied an additional glue?",
+          "Anyway, the additional nodes will be ignored"})
+      setnext(lead, nil)
     end
     table.insert(underline_types, lead)
-    b.head = lead.next
-    node.flush_node(b)
+    setList(b, after)
+    flush_node(b)
   end
-  token.put_next(token.new(#underline_types, char_given))
+  put_next(tokennew(#underline_types, char_given))
+  underline_strict_flag[#underline_types] = strict_flag
+  underline_over_flag[#underline_types] = over_flag
 end
+%    \end{macrocode}
+% In \verb+append_to_vlist_filter+ we can not access the list attributes,
+% so we just take the current ones. They might be incorrect if the attribute
+% changes in the vlist, so we record the original value in a property then.
+%    \begin{macrocode}
 local function set_underline()
-  local j
+  local j, props
+  for i=texnest.ptr,0,-1 do
+    local mode = texnest[i].mode
+    if mode == vmode or mode == -vmode then
+      local head = todirect(texnest[i].head)
+      local head_props = properties[head]
+      if not head_props then
+        head_props = {}
+        properties[head] = head_props
+      end
+      props = head_props.luaul_attributes
+      if not props then
+        props = {}
+        head_props.luaul_attributes = props
+        break
+      end
+    end
+  end
   for i=1,#underlineattrs do
     local attr = underlineattrs[i]
     if tex.attribute[attr] == -0x7FFFFFFF then
@@ -339,13 +426,14 @@
         "luaul" .. tostring(#underlineattrs+1))
     underlineattrs[#underlineattrs+1] = j
   end
-  tex.attribute[j] = token.scan_int()
+  props[j] = props[j] or -0x7FFFFFFF
+  tex.attribute[j] = scan_int()
 end
 %    \end{macrocode}
 % \changes{0.0.2}{2020-03-15}{Add command to disable active underlining}
 %    \begin{macrocode}
 local function reset_underline()
-  local reset_all = token.scan_keyword'*'
+  local reset_all = scan_keyword'*'
   local j
   for i=1,#underlineattrs do
     local attr = underlineattrs[i]
@@ -359,9 +447,9 @@
   end
   if not j then
     if not reset_all then
-      tex.error("No underline active", {"You tried to disable \z
-            underlining but underlining was not active",
-            "in the first place. Maybe you wanted to ensure that \z
+      texerror("No underline active", {"You tried to disable \z
+            underlining but underlining was not active in the first",
+            "place. Maybe you wanted to ensure that \z
             no underling can be active anymore?", "Then you should \z
             append a *."})
     end
@@ -370,7 +458,6 @@
   tex.attribute[j] = -0x7FFFFFFF
 end
 local functions = lua.get_functions_table()
-local set_lua = token.set_lua
 local new_underline_type_func =
     luatexbase.new_luafunction"luaul.new_underline_type"
 local set_underline_func =
@@ -384,77 +471,269 @@
 functions[set_underline_func] = set_underline
 functions[reset_underline_func] = reset_underline
 
-local add_underline_h
-local function add_underline_v(head, attr)
-  for n in node.traverse(head) do
-    if head.id == hlist_t then
-      add_underline_h(n, attr)
-    elseif head.id == vlist_t then
-      add_underline_v(n.head, attr)
+%    \end{macrocode}
+% \changes{0.0.3}{2020-03-17}{Use glue instead of kern to allow handling
+% unset lists}
+% A little helper to measure box contents and creating a glue
+% node with inverted dimensions.
+%    \begin{macrocode}
+local stretch_fi = {}
+local shrink_fi = {}
+local function fil_levels(n)
+  for i=0,4 do
+    stretch_fi[i], shrink_fi[i] = 0, 0
+  end
+  for n in traverse_id(glue_t, n) do
+    local w, st, sh, sto, sho = getglue(n)
+    stretch_fi[sto] = stretch_fi[sto] + st
+    shrink_fi[sho] = shrink_fi[sho] + sh
+  end
+  local stretch, shrink = 0, 0
+  for i=0,4 do
+    if stretch_fi[i] ~= 0 then
+      stretch = i
     end
+    if shrink_fi[i] ~= 0 then
+      shrink = i
+    end
   end
+  return stretch, shrink
 end
-function add_underline_h(head, attr)
-  local used = false
-  node.slide(head.head)
+local function new_glue_neg_dimensions(n, t,
+                                       stretch_order, shrink_order)
+  local g = nodenew(glue_t)
+  local w = -dimensions(n, t)
+  setglue(g, w)
+%  setglue(g, -dimensions(n, t), 0, 0, stretch_order, shrink_order)
+  setnext(g, n)
+  setglue(g, w, -dimensions(1, 1, stretch_order, g, t),
+                   dimensions(1, 2, shrink_order, g, t),
+                   stretch_order, shrink_order)
+  setnext(g, nil)
+  return g
+end
+
+%    \end{macrocode}
+% \changes{0.0.3}{2020-03-17}{Make streight lines over hboxes}
+% \changes{0.0.4}{2020-03-31}{Consistently respect height and depth}
+% Now the actual undelining
+%    \begin{macrocode}
+local add_underline_hlist, add_underline_hbox, add_underline_vbox
+local function add_underline_vlist(head, attr, outervalue)
+  local iter, state, n = traverse_list(head) -- FIXME: unset nodes
+  local t
+  n, t = iter(state, n)
+  while n ~= nil do
+    local real_new_value = has_attribute(n, attr)
+    local new_value = real_new_value ~= outervalue
+                        and real_new_value or nil
+    if underline_strict_flag[new_value] or not new_value then
+      if t == hlist_t then
+        add_underline_hbox(n, attr, real_new_value)
+      elseif t == vlist_t then
+        add_underline_vbox(n, attr, real_new_value)
+      end
+      n, t = iter(state, n)
+    elseif real_new_value <= 0 then
+      n, t = iter(state, n)
+    else
+      local nn
+      nn, t = iter(state, n)
+      local prev, next = getboth(n)
+      setboth(n, nil, nil)
+      local shift = getshift(n)
+      setshift(n, 0)
+      local new_list = hpack((add_underline_hlist(n, attr)))
+      setheight(new_list, getheight(n))
+      setdepth(new_list, getdepth(n))
+      setshift(new_list, shift)
+      setlink(prev, new_list, next)
+      set_attribute(new_list, attr, 0)
+      if n == head then
+        head = new_list
+      end
+      n = nn
+    end
+  end
+  return head
+end
+function add_underline_vbox(head, attr, outervalue)
+  if outervalue and outervalue <= 0 then return end
+  setList(head, add_underline_vlist(getList(head), attr, outervalue))
+  set_attribute(head, attr, outervalue and -outervalue or 0)
+end
+function add_underline_hlist(head, attr, outervalue)
+  local max_height, max_depth
+  slide(head)
   local last_value
   local first
-  for n in node.traverse(head.head) do
-    local new_value = node.has_attribute(n, attr)
-    if n.id == hlist_t then
-      new_value = nil
-      add_underline_h(n, attr)
-    elseif n.id == vlist_t then
-      new_value = nil
-      add_underline_v(n.head, attr)
-    elseif n.id == kern_t and n.subtype == 0 then
-      if n.next and not node.has_attribute(n.next, attr) then
+  local shrink_order, stretch_order
+  for n, id, subtype in traverse(head) do
+    local real_new_value = has_attribute(n, attr)
+    local new_value
+    if real_new_value then
+      if real_new_value > 0 then
+        set_attribute(n, attr, -real_new_value)
+        new_value = real_new_value ~= outervalue
+                      and real_new_value or nil
+      end
+    else
+      set_attribute(n, attr, 0)
+    end
+    if id == hlist_t then
+      if underline_strict_flag[new_value]
+          or subtype == 3 or not new_value then
+        add_underline_hbox(n, attr, real_new_value)
         new_value = nil
+      end
+    elseif id == vlist_t then
+      if underline_strict_flag[new_value] or not new_value then
+        add_underline_vbox(n, attr, real_new_value)
+        new_value = nil
+      end
+    elseif id == kern_t and subtype == 0 then
+      local after = getnext(n)
+      if after then
+        local next_value = has_attribute(after, attr)
+        if next_value == outervalue or not next_value then
+          new_value = nil
+        else
+          new_value = last_value
+        end
       else
         new_value = last_value
       end
-    elseif n.id == glue_t and (
-        n.subtype == 8 or
-        n.subtype == 9 or
-        n.subtype == 15 or
+    elseif id == glue_t and (
+        subtype == 8 or
+        subtype == 9 or
+        subtype == 15 or
     false) then
       new_value = nil
     end
     if last_value ~= new_value then
+      if not stretch_order then
+        stretch_order, shrink_order = fil_levels(head)
+      end
       if last_value then
-        local width = node.rangedimensions(head, first, n)
-        local kern = node.new(kern_t)
-        kern.kern = -width
-        local lead = node.copy(underline_types[last_value])
-        lead.width = width
-        head.head = node.insert_before(head.head, first, lead)
-        node.insert_after(head, lead, kern)
+        local glue = new_glue_neg_dimensions(first, n,
+            stretch_order, shrink_order)
+        local w, st, sh = getglue(glue)
+        local lead = nodecopy(underline_types[last_value])
+        setglue(lead, -w, -st, -sh, stretch_order, shrink_order)
+        if underline_over_flag[last_value] then
+          head = insert_before(head, n, glue)
+          insert_after(head, glue, lead)
+        else
+          head = insert_before(head, first, lead)
+          insert_after(head, lead, glue)
+        end
       end
       if new_value then
         first = n
+        local box = getleader(underline_types[new_value])
+        if not max_height or getheight(box) > max_height then
+          max_height = getheight(box)
+        end
+        if not max_depth or getdepth(box) > max_depth then
+          max_depth = getdepth(box)
+        end
       end
       last_value = new_value
     end
   end
   if last_value then
-    local width = node.rangedimensions(head, first)
-    local kern = node.new(kern_t)
-    kern.kern = -width
-    local lead = node.copy(underline_types[last_value])
-    lead.width = width
-    head.head = node.insert_before(head.head, first, lead)
-    node.insert_after(head, lead, kern)
+    local glue = new_glue_neg_dimensions(first, nil,
+        stretch_order, shrink_order)
+    local w, st, sh = getglue(glue)
+    local lead = nodecopy(underline_types[last_value])
+    setglue(lead, -w, -st, -sh, stretch_order, shrink_order)
+    if underline_over_flag[last_value] then
+      insert_before(head, nil, glue)
+      insert_after(head, glue, lead)
+    else
+      head = insert_before(head, first, lead)
+      insert_after(head, lead, glue)
+    end
   end
+  return head, max_height, max_depth
 end
-local function filter(b, loc, prev, mirror)
-  for i = 1, #underlineattrs do
-    add_underline_v(b, underlineattrs[i])
+function add_underline_hbox(head, attr, outervalue, set_height_depth)
+  if outervalue and outervalue <= 0 then return end
+  local new_head, new_height, new_depth
+      = add_underline_hlist(getList(head), attr, outervalue)
+  setList(head, new_head)
+  if set_height_depth then
+    if new_height and getheight(head) < new_height then
+      setheight(head, new_height)
+    end
+    if new_depth and getdepth(head) < new_depth then
+      setdepth(head, new_depth)
+    end
   end
-  return true
+  set_attribute(head, attr, outervalue and -outervalue or 0)
 end
 require'pre_append_to_vlist_filter'
 luatexbase.add_to_callback('pre_append_to_vlist_filter',
-                           filter, 'add underlines to list')
+    function(b, loc, prev, mirror)
+      local props = properties[todirect(texnest.top.head)]
+      props = props and props.luaul_attributes
+      b = todirect(b)
+      if loc == "post_linebreak" then
+        for i = 1, #underlineattrs do
+          local attr = underlineattrs[i]
+          local current = props and props[attr] or tex.attribute[attr]
+          if current == -0x7FFFFFFF then
+            current = nil
+          end
+          add_underline_hbox(b, underlineattrs[i], current, true)
+        end
+      else
+        for i = 1, #underlineattrs do
+          local attr = underlineattrs[i]
+          local current = props and props[attr] or tex.attribute[attr]
+          local b_attr = has_attribute(b, attr)
+          if b_attr and b_attr ~= current then
+            local shift = getshift(b)
+            setshift(b, 0)
+            b = hpack((add_underline_hlist(b, attr)))
+            setshift(b, shift)
+            set_attribute(b, attr, 0)
+          end
+        end
+      end
+      return tonode(b)
+    end, 'add underlines to list')
+luatexbase.add_to_callback('hpack_filter',
+    function(head, group, size, pack, dir, attr)
+      head = todirect(head)
+      for i = 1, #underlineattrs do
+        local ulattr = underlineattrs[i]
+        local current
+        for n in node.traverse(attr) do
+          if n.number == ulattr then
+            current = n.value
+          end
+        end
+        head = add_underline_hlist(head, ulattr, current)
+      end
+      return tonode(head)
+    end, 'add underlines to list')
+luatexbase.add_to_callback('vpack_filter',
+    function(head, group, size, pack, maxdepth, dir, attr)
+%      if true then return head end
+      head = todirect(head)
+      for i = 1, #underlineattrs do
+        local ulattr = underlineattrs[i]
+        local current
+        for n in node.traverse(attr) do
+          if n.number == ulattr then
+            current = n.value
+          end
+        end
+        head = add_underline_vlist(head, ulattr, current)
+      end
+      return tonode(head)
+    end, 'add underlines to list')
 %    \end{macrocode}
 % \iffalse
 %</luacode>
@@ -469,7 +748,7 @@
 \NeedsTeXFormat{LaTeX2e}
 \ProvidesPackage
   {lua-ul}
-  [2020/03/15 v0.0.2 Underlining and related functionality for LuaTeX]
+  [2020/03/31 v0.0.4 Underlining and related functionality for LuaTeX]
 
 % \fi
 % Only \LuaLaTeX{} is supported.
@@ -516,13 +795,14 @@
 %    \end{macrocode}
 % The main macro.
 %    \begin{macrocode}
-\NewDocumentCommand\newunderlinetype{mO{\luaul at defaultcontext}m}{%
-  \newcommand#1{}% "Reserve" the name
-  \protected\def#1{%
+\NewDocumentCommand\newunderlinetype
+    { E{*}{{}} m O{\luaul at defaultcontext} m }{%
+  \newcommand#2{}% "Reserve" the name
+  \protected\def#2{%
     \expandafter\luaul at maybedefineuse
-      \expanded{{\csstring#1@@#2}}%
+      \expanded{{\csstring#2@@#3}}%
       {\LuaULSetUnderline
-        \LuaULNewUnderlineType\hbox{#3\hskip0pt}%
+        \LuaULNewUnderlineType#1\hbox{#4\hskip0pt}%
   }}%
 }
 \ifluaul at predefined

Modified: trunk/Master/texmf-dist/tex/lualatex/lua-ul/lua-ul.lua
===================================================================
--- trunk/Master/texmf-dist/tex/lualatex/lua-ul/lua-ul.lua	2020-04-11 23:00:22 UTC (rev 54689)
+++ trunk/Master/texmf-dist/tex/lualatex/lua-ul/lua-ul.lua	2020-04-11 23:00:41 UTC (rev 54690)
@@ -17,15 +17,74 @@
 --
 -- and version 1.3 or later is part of all distributions of
 -- LaTeX version 2005/12/01 or later.
+local unset_t = node.id'unset'
 local hlist_t = node.id'hlist'
 local vlist_t = node.id'vlist'
 local kern_t = node.id'kern'
 local glue_t = node.id'glue'
 
+local properties = node.direct.get_properties_table()
+
+local has_attribute = node.direct.has_attribute
+local set_attribute = node.direct.set_attribute
+local dimensions = node.direct.dimensions
+local flush_node = node.direct.flush_node
+local getboth = node.direct.getboth
+local getfield = node.direct.getfield
+local getglue = node.direct.getglue
+local getleader = node.direct.getleader
+local getlist = node.direct.getlist
+local setheight = node.direct.setheight
+local setdepth = node.direct.setdepth
+local getheight = node.direct.getheight
+local getdepth = node.direct.getdepth
+local getnext = node.direct.getnext
+local getshift = node.direct.getshift
+local insert_after = node.direct.insert_after
+local insert_before = node.direct.insert_before
+local nodecopy = node.direct.copy
+local nodenew = node.direct.new
+local setboth = node.direct.setboth
+local setlink = node.direct.setlink
+local hpack = node.direct.hpack
+local setfield = node.direct.setfield
+local slide = node.direct.slide
+local setglue = node.direct.setglue
+local setnext = node.direct.setnext
+local setshift = node.direct.setshift
+local todirect = node.direct.todirect
+local tonode = node.direct.tonode
+local traverse = node.direct.traverse
+local traverse_id = node.direct.traverse_id
+local traverse_list = node.direct.traverse_list
+local getList = function(n) return getfield(n, 'list') end
+local setList = function(n, h) return setfield(n, 'list', h) end
+
+local tokennew = token.new
+local set_lua = token.set_lua
+local scan_keyword = token.scan_keyword
+local scan_list = token.scan_list
+local scan_int = token.scan_int
+local put_next = token.put_next
+local texerror = tex.error
+
 local char_given = token.command_id'char_given'
 
 local underlineattrs = {}
 local underline_types = {}
+local underline_strict_flag = {}
+local underline_over_flag = {}
+
+local vmode do
+  for k, v in pairs(tex.getmodevalues()) do
+  if v == "vertical" then
+      vmode = k
+      break
+    end
+  end
+end
+local texnest = tex.nest
+
 local saved_values = {}
 local function new_underline_type()
   for i=1,#underlineattrs do
@@ -33,30 +92,53 @@
     saved_values[i] = tex.attribute[attr]
     tex.attribute[attr] = -0x7FFFFFFF
   end
-  local b = token.scan_list()
+  local strict_flag = scan_keyword'strict'
+  local over_flag = scan_keyword'over'
+  local b = todirect(scan_list())
   for i=1,#underlineattrs do
     tex.attribute[underlineattrs[i]] = saved_values[i]
   end
-  local lead = b.head
-  if not lead.leader then
-    tex.error("Leader required", {"An underline type has to \z
+  local lead = getlist(b)
+  if not getleader(lead) then
+    texerror("Leader required", {"An underline type has to \z
       be defined by leader. You should use one of the", "commands \z
       \\leaders, \\cleaders, or \\xleader, or \\gleaders here."})
   else
-    if lead.next then
-    tex.error("Too many nodes", {"An underline type can only be \z
-        defined by a single leaders specification,", "not by \z
-        multiple nodes. Maybe you supplied an additional glue?",
-        "Anyway, the additional nodes will be ignored"})
+    local after = getnext(lead)
+    if after then
+      texerror("Too many nodes", {"An underline type can only be \z
+          defined by a single leaders specification,", "not by \z
+          multiple nodes. Maybe you supplied an additional glue?",
+          "Anyway, the additional nodes will be ignored"})
+      setnext(lead, nil)
     end
     table.insert(underline_types, lead)
-    b.head = lead.next
-    node.flush_node(b)
+    setList(b, after)
+    flush_node(b)
   end
-  token.put_next(token.new(#underline_types, char_given))
+  put_next(tokennew(#underline_types, char_given))
+  underline_strict_flag[#underline_types] = strict_flag
+  underline_over_flag[#underline_types] = over_flag
 end
 local function set_underline()
-  local j
+  local j, props
+  for i=texnest.ptr,0,-1 do
+    local mode = texnest[i].mode
+    if mode == vmode or mode == -vmode then
+      local head = todirect(texnest[i].head)
+      local head_props = properties[head]
+      if not head_props then
+        head_props = {}
+        properties[head] = head_props
+      end
+      props = head_props.luaul_attributes
+      if not props then
+        props = {}
+        head_props.luaul_attributes = props
+        break
+      end
+    end
+  end
   for i=1,#underlineattrs do
     local attr = underlineattrs[i]
     if tex.attribute[attr] == -0x7FFFFFFF then
@@ -69,10 +151,11 @@
         "luaul" .. tostring(#underlineattrs+1))
     underlineattrs[#underlineattrs+1] = j
   end
-  tex.attribute[j] = token.scan_int()
+  props[j] = props[j] or -0x7FFFFFFF
+  tex.attribute[j] = scan_int()
 end
 local function reset_underline()
-  local reset_all = token.scan_keyword'*'
+  local reset_all = scan_keyword'*'
   local j
   for i=1,#underlineattrs do
     local attr = underlineattrs[i]
@@ -86,9 +169,9 @@
   end
   if not j then
     if not reset_all then
-      tex.error("No underline active", {"You tried to disable \z
-            underlining but underlining was not active",
-            "in the first place. Maybe you wanted to ensure that \z
+      texerror("No underline active", {"You tried to disable \z
+            underlining but underlining was not active in the first",
+            "place. Maybe you wanted to ensure that \z
             no underling can be active anymore?", "Then you should \z
             append a *."})
     end
@@ -97,7 +180,6 @@
   tex.attribute[j] = -0x7FFFFFFF
 end
 local functions = lua.get_functions_table()
-local set_lua = token.set_lua
 local new_underline_type_func =
     luatexbase.new_luafunction"luaul.new_underline_type"
 local set_underline_func =
@@ -111,77 +193,256 @@
 functions[set_underline_func] = set_underline
 functions[reset_underline_func] = reset_underline
 
-local add_underline_h
-local function add_underline_v(head, attr)
-  for n in node.traverse(head) do
-    if head.id == hlist_t then
-      add_underline_h(n, attr)
-    elseif head.id == vlist_t then
-      add_underline_v(n.head, attr)
+local stretch_fi = {}
+local shrink_fi = {}
+local function fil_levels(n)
+  for i=0,4 do
+    stretch_fi[i], shrink_fi[i] = 0, 0
+  end
+  for n in traverse_id(glue_t, n) do
+    local w, st, sh, sto, sho = getglue(n)
+    stretch_fi[sto] = stretch_fi[sto] + st
+    shrink_fi[sho] = shrink_fi[sho] + sh
+  end
+  local stretch, shrink = 0, 0
+  for i=0,4 do
+    if stretch_fi[i] ~= 0 then
+      stretch = i
     end
+    if shrink_fi[i] ~= 0 then
+      shrink = i
+    end
   end
+  return stretch, shrink
 end
-function add_underline_h(head, attr)
-  local used = false
-  node.slide(head.head)
+local function new_glue_neg_dimensions(n, t,
+                                       stretch_order, shrink_order)
+  local g = nodenew(glue_t)
+  local w = -dimensions(n, t)
+  setglue(g, w)
+  setnext(g, n)
+  setglue(g, w, -dimensions(1, 1, stretch_order, g, t),
+                   dimensions(1, 2, shrink_order, g, t),
+                   stretch_order, shrink_order)
+  setnext(g, nil)
+  return g
+end
+
+local add_underline_hlist, add_underline_hbox, add_underline_vbox
+local function add_underline_vlist(head, attr, outervalue)
+  local iter, state, n = traverse_list(head) -- FIXME: unset nodes
+  local t
+  n, t = iter(state, n)
+  while n ~= nil do
+    local real_new_value = has_attribute(n, attr)
+    local new_value = real_new_value ~= outervalue
+                        and real_new_value or nil
+    if underline_strict_flag[new_value] or not new_value then
+      if t == hlist_t then
+        add_underline_hbox(n, attr, real_new_value)
+      elseif t == vlist_t then
+        add_underline_vbox(n, attr, real_new_value)
+      end
+      n, t = iter(state, n)
+    elseif real_new_value <= 0 then
+      n, t = iter(state, n)
+    else
+      local nn
+      nn, t = iter(state, n)
+      local prev, next = getboth(n)
+      setboth(n, nil, nil)
+      local shift = getshift(n)
+      setshift(n, 0)
+      local new_list = hpack((add_underline_hlist(n, attr)))
+      setheight(new_list, getheight(n))
+      setdepth(new_list, getdepth(n))
+      setshift(new_list, shift)
+      setlink(prev, new_list, next)
+      set_attribute(new_list, attr, 0)
+      if n == head then
+        head = new_list
+      end
+      n = nn
+    end
+  end
+  return head
+end
+function add_underline_vbox(head, attr, outervalue)
+  if outervalue and outervalue <= 0 then return end
+  setList(head, add_underline_vlist(getList(head), attr, outervalue))
+  set_attribute(head, attr, outervalue and -outervalue or 0)
+end
+function add_underline_hlist(head, attr, outervalue)
+  local max_height, max_depth
+  slide(head)
   local last_value
   local first
-  for n in node.traverse(head.head) do
-    local new_value = node.has_attribute(n, attr)
-    if n.id == hlist_t then
-      new_value = nil
-      add_underline_h(n, attr)
-    elseif n.id == vlist_t then
-      new_value = nil
-      add_underline_v(n.head, attr)
-    elseif n.id == kern_t and n.subtype == 0 then
-      if n.next and not node.has_attribute(n.next, attr) then
+  local shrink_order, stretch_order
+  for n, id, subtype in traverse(head) do
+    local real_new_value = has_attribute(n, attr)
+    local new_value
+    if real_new_value then
+      if real_new_value > 0 then
+        set_attribute(n, attr, -real_new_value)
+        new_value = real_new_value ~= outervalue
+                      and real_new_value or nil
+      end
+    else
+      set_attribute(n, attr, 0)
+    end
+    if id == hlist_t then
+      if underline_strict_flag[new_value]
+          or subtype == 3 or not new_value then
+        add_underline_hbox(n, attr, real_new_value)
         new_value = nil
+      end
+    elseif id == vlist_t then
+      if underline_strict_flag[new_value] or not new_value then
+        add_underline_vbox(n, attr, real_new_value)
+        new_value = nil
+      end
+    elseif id == kern_t and subtype == 0 then
+      local after = getnext(n)
+      if after then
+        local next_value = has_attribute(after, attr)
+        if next_value == outervalue or not next_value then
+          new_value = nil
+        else
+          new_value = last_value
+        end
       else
         new_value = last_value
       end
-    elseif n.id == glue_t and (
-        n.subtype == 8 or
-        n.subtype == 9 or
-        n.subtype == 15 or
+    elseif id == glue_t and (
+        subtype == 8 or
+        subtype == 9 or
+        subtype == 15 or
     false) then
       new_value = nil
     end
     if last_value ~= new_value then
+      if not stretch_order then
+        stretch_order, shrink_order = fil_levels(head)
+      end
       if last_value then
-        local width = node.rangedimensions(head, first, n)
-        local kern = node.new(kern_t)
-        kern.kern = -width
-        local lead = node.copy(underline_types[last_value])
-        lead.width = width
-        head.head = node.insert_before(head.head, first, lead)
-        node.insert_after(head, lead, kern)
+        local glue = new_glue_neg_dimensions(first, n,
+            stretch_order, shrink_order)
+        local w, st, sh = getglue(glue)
+        local lead = nodecopy(underline_types[last_value])
+        setglue(lead, -w, -st, -sh, stretch_order, shrink_order)
+        if underline_over_flag[last_value] then
+          head = insert_before(head, n, glue)
+          insert_after(head, glue, lead)
+        else
+          head = insert_before(head, first, lead)
+          insert_after(head, lead, glue)
+        end
       end
       if new_value then
         first = n
+        local box = getleader(underline_types[new_value])
+        if not max_height or getheight(box) > max_height then
+          max_height = getheight(box)
+        end
+        if not max_depth or getdepth(box) > max_depth then
+          max_depth = getdepth(box)
+        end
       end
       last_value = new_value
     end
   end
   if last_value then
-    local width = node.rangedimensions(head, first)
-    local kern = node.new(kern_t)
-    kern.kern = -width
-    local lead = node.copy(underline_types[last_value])
-    lead.width = width
-    head.head = node.insert_before(head.head, first, lead)
-    node.insert_after(head, lead, kern)
+    local glue = new_glue_neg_dimensions(first, nil,
+        stretch_order, shrink_order)
+    local w, st, sh = getglue(glue)
+    local lead = nodecopy(underline_types[last_value])
+    setglue(lead, -w, -st, -sh, stretch_order, shrink_order)
+    if underline_over_flag[last_value] then
+      insert_before(head, nil, glue)
+      insert_after(head, glue, lead)
+    else
+      head = insert_before(head, first, lead)
+      insert_after(head, lead, glue)
+    end
   end
+  return head, max_height, max_depth
 end
-local function filter(b, loc, prev, mirror)
-  for i = 1, #underlineattrs do
-    add_underline_v(b, underlineattrs[i])
+function add_underline_hbox(head, attr, outervalue, set_height_depth)
+  if outervalue and outervalue <= 0 then return end
+  local new_head, new_height, new_depth
+      = add_underline_hlist(getList(head), attr, outervalue)
+  setList(head, new_head)
+  if set_height_depth then
+    if new_height and getheight(head) < new_height then
+      setheight(head, new_height)
+    end
+    if new_depth and getdepth(head) < new_depth then
+      setdepth(head, new_depth)
+    end
   end
-  return true
+  set_attribute(head, attr, outervalue and -outervalue or 0)
 end
 require'pre_append_to_vlist_filter'
 luatexbase.add_to_callback('pre_append_to_vlist_filter',
-                           filter, 'add underlines to list')
+    function(b, loc, prev, mirror)
+      local props = properties[todirect(texnest.top.head)]
+      props = props and props.luaul_attributes
+      b = todirect(b)
+      if loc == "post_linebreak" then
+        for i = 1, #underlineattrs do
+          local attr = underlineattrs[i]
+          local current = props and props[attr] or tex.attribute[attr]
+          if current == -0x7FFFFFFF then
+            current = nil
+          end
+          add_underline_hbox(b, underlineattrs[i], current, true)
+        end
+      else
+        for i = 1, #underlineattrs do
+          local attr = underlineattrs[i]
+          local current = props and props[attr] or tex.attribute[attr]
+          local b_attr = has_attribute(b, attr)
+          if b_attr and b_attr ~= current then
+            local shift = getshift(b)
+            setshift(b, 0)
+            b = hpack((add_underline_hlist(b, attr)))
+            setshift(b, shift)
+            set_attribute(b, attr, 0)
+          end
+        end
+      end
+      return tonode(b)
+    end, 'add underlines to list')
+luatexbase.add_to_callback('hpack_filter',
+    function(head, group, size, pack, dir, attr)
+      head = todirect(head)
+      for i = 1, #underlineattrs do
+        local ulattr = underlineattrs[i]
+        local current
+        for n in node.traverse(attr) do
+          if n.number == ulattr then
+            current = n.value
+          end
+        end
+        head = add_underline_hlist(head, ulattr, current)
+      end
+      return tonode(head)
+    end, 'add underlines to list')
+luatexbase.add_to_callback('vpack_filter',
+    function(head, group, size, pack, maxdepth, dir, attr)
+      head = todirect(head)
+      for i = 1, #underlineattrs do
+        local ulattr = underlineattrs[i]
+        local current
+        for n in node.traverse(attr) do
+          if n.number == ulattr then
+            current = n.value
+          end
+        end
+        head = add_underline_vlist(head, ulattr, current)
+      end
+      return tonode(head)
+    end, 'add underlines to list')
 -- 
 --
 -- End of file `lua-ul.lua'.

Modified: trunk/Master/texmf-dist/tex/lualatex/lua-ul/lua-ul.sty
===================================================================
--- trunk/Master/texmf-dist/tex/lualatex/lua-ul/lua-ul.sty	2020-04-11 23:00:22 UTC (rev 54689)
+++ trunk/Master/texmf-dist/tex/lualatex/lua-ul/lua-ul.sty	2020-04-11 23:00:41 UTC (rev 54690)
@@ -20,7 +20,7 @@
 \NeedsTeXFormat{LaTeX2e}
 \ProvidesPackage
   {lua-ul}
-  [2020/03/15 v0.0.2 Underlining and related functionality for LuaTeX]
+  [2020/03/31 v0.0.4 Underlining and related functionality for LuaTeX]
 
 \ifx\directlua\undefined
   \PackageError{lua-ul}{LuaLaTeX required}%
@@ -47,13 +47,14 @@
       \the\LuaCol at Attribute
     \fi
   }
-\NewDocumentCommand\newunderlinetype{mO{\luaul at defaultcontext}m}{%
-  \newcommand#1{}% "Reserve" the name
-  \protected\def#1{%
+\NewDocumentCommand\newunderlinetype
+    { E{*}{{}} m O{\luaul at defaultcontext} m }{%
+  \newcommand#2{}% "Reserve" the name
+  \protected\def#2{%
     \expandafter\luaul at maybedefineuse
-      \expanded{{\csstring#1@@#2}}%
+      \expanded{{\csstring#2@@#3}}%
       {\LuaULSetUnderline
-        \LuaULNewUnderlineType\hbox{#3\hskip0pt}%
+        \LuaULNewUnderlineType#1\hbox{#4\hskip0pt}%
   }}%
 }
 \ifluaul at predefined

Modified: trunk/Master/texmf-dist/tex/lualatex/lua-ul/pre_append_to_vlist_filter.lua
===================================================================
--- trunk/Master/texmf-dist/tex/lualatex/lua-ul/pre_append_to_vlist_filter.lua	2020-04-11 23:00:22 UTC (rev 54689)
+++ trunk/Master/texmf-dist/tex/lualatex/lua-ul/pre_append_to_vlist_filter.lua	2020-04-11 23:00:41 UTC (rev 54690)
@@ -51,7 +51,7 @@
     current = box
   end
   return call_callback("append_to_vlist_filter",
-                       box, locationcode, prevdepth, mirrored)
+                       current, locationcode, prevdepth, mirrored)
 end
 
 callback_define('append_to_vlist_filter',



More information about the tex-live-commits mailing list.