texlive[74103] Master/texmf-dist: marginalia (18feb25)

commits+karl at tug.org commits+karl at tug.org
Tue Feb 18 22:07:08 CET 2025


Revision: 74103
          https://tug.org/svn/texlive?view=revision&revision=74103
Author:   karl
Date:     2025-02-18 22:07:08 +0100 (Tue, 18 Feb 2025)
Log Message:
-----------
marginalia (18feb25)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/lualatex/marginalia/marginalia-doc-example.tex
    trunk/Master/texmf-dist/doc/lualatex/marginalia/marginalia-doc-ysep-explanation.pdf
    trunk/Master/texmf-dist/doc/lualatex/marginalia/marginalia-doc.pdf
    trunk/Master/texmf-dist/source/lualatex/marginalia/marginalia.dtx
    trunk/Master/texmf-dist/tex/lualatex/marginalia/marginalia.lua
    trunk/Master/texmf-dist/tex/lualatex/marginalia/marginalia.sty

Modified: trunk/Master/texmf-dist/doc/lualatex/marginalia/marginalia-doc-example.tex
===================================================================
--- trunk/Master/texmf-dist/doc/lualatex/marginalia/marginalia-doc-example.tex	2025-02-18 21:06:58 UTC (rev 74102)
+++ trunk/Master/texmf-dist/doc/lualatex/marginalia/marginalia-doc-example.tex	2025-02-18 21:07:08 UTC (rev 74103)
@@ -14,4 +14,3 @@
   serif, ragged left, and bottom-aligned.}
 
 \end{document}
-LUACODE

Modified: trunk/Master/texmf-dist/doc/lualatex/marginalia/marginalia-doc-ysep-explanation.pdf
===================================================================
(Binary files differ)

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

Modified: trunk/Master/texmf-dist/source/lualatex/marginalia/marginalia.dtx
===================================================================
--- trunk/Master/texmf-dist/source/lualatex/marginalia/marginalia.dtx	2025-02-18 21:06:58 UTC (rev 74102)
+++ trunk/Master/texmf-dist/source/lualatex/marginalia/marginalia.dtx	2025-02-18 21:07:08 UTC (rev 74103)
@@ -973,7 +973,7 @@
 % Package identification/version information.
 %    \begin{macrocode}
 \NeedsTeXFormat{LaTeX2e}[2020-02-02]
-\ProvidesExplPackage{marginalia}{2025-02-17}{0.80.1}
+\ProvidesExplPackage{marginalia}{2025-02-18}{0.80.2}
   {Non-floating marginal content for LuaLaTeX}
 %    \end{macrocode}
 % Check that Lua\TeX\ is in use.
@@ -1923,9 +1923,1345 @@
 %
 %
 %
-LUACODE
+% \section{Implementation (Lua backend)}
 %
+%    \begin{macrocode}
+%<*lua>
+%    \end{macrocode}
 %
 %
+%
+% \subsection{Global variables}
+%
+% Global tables for page_data and item_data.
+%    \begin{macrocode}
+local PAGE_DATA_MAIN_TABLE = {}
+local ITEM_DATA_MAIN_TABLE = {}
+%    \end{macrocode}
+% Global tables for compiling reports.
+%    \begin{macrocode}
+local PROBLEM_REPORT_TABLE = {}
+local PAGE_CHANGE_REPORT_TABLE = {}
+local ITEM_CHANGE_REPORT_TABLE = {}
+%    \end{macrocode}
+% Global configuration for reports.
+% \begin{macrocode}
+local PROBLEM_REPORT_MAX_LENGTH = 40
+local PAGE_CHANGE_REPORT_MAX_LENGTH = 10
+local ITEM_CHANGE_REPORT_MAX_LENGTH = 10
+%    \end{macrocode}
+%
+%
+%
+% \subsection{Constants}
+%
+% Type constants. These match the possible values for the type key.
+%    \begin{macrocode}
+local TYPE_NORMAL = 1
+local TYPE_FIXED = 2
+local TYPE_OPTFIXED = 3
+%    \end{macrocode}
+% Position constants. These match the possible values for the pos key.
+%    \begin{macrocode}
+local POS_AUTO = 1
+local POS_REVERSE = 2
+local POS_LEFT = 3
+local POS_RIGHT = 4
+local POS_NEAREST = 5
+%    \end{macrocode}
+%
+%
+%
+% \subsection{Keys for tables}
+%
+% The strings listed in this subsection are constants used to index the tables. Also listed are the types of
+% values that are indexed by each key. Note that values listed below as "dimensions" are actually integers, giving the
+% dimension in TeX scaled points (sp)
+%
+%
+%
+% \subsubsection{Keys for both page and item data tables}
+%
+% Integer: Absolute page number in output file (not on-page number), used in both page_data and item_data tables
+%    \begin{macrocode}
+local KEY_ABSPAGENO = 'abspageno'
+%    \end{macrocode}
+% Boolean: Used to mark page_data or item_data as checked when the .aux file is read back at the end of the document
+%    \begin{macrocode}
+local KEY_CHECKED = 'checked'
+%    \end{macrocode}
+%
+%
+%
+% \subsubsection{Keys for page data tables, layout etc.}
+%
+% Integer: Used only to distinguish instances of data written to .aux file
+%    \begin{macrocode}
+local KEY_PAGEDATANO = 'pagedatano'
+%    \end{macrocode}
+% Dimensions: Value of next two will always be equivalent of \qty{1}{\inch}, but it is simpler to keep all geometry
+% data together.
+%    \begin{macrocode}
+local KEY_HOFFSETORIGIN = 'hoffsetorigin'
+local KEY_VOFFSETORIGIN = 'voffsetorigin'
+%    \end{macrocode}
+% Dimensions: corresponding to obvious LaTeX dimensions
+%    \begin{macrocode}
+local KEY_HOFFSET = 'hoffset'
+local KEY_VOFFSET = 'voffset'
+local KEY_PAPERHEIGHT = 'paperheight'
+local KEY_ODDSIDEMARGIN = 'oddsidemargin'
+local KEY_EVENSIDEMARGIN = 'evensidemargin'
+local KEY_TEXTWIDTH = 'textwidth'
+local KEY_COLUMNWIDTH = 'columnwidth'
+local KEY_COLUMNSEP = 'columnsep'
+%    \end{macrocode}
+% Integer: either \(1\) or \(2\), depending on whether LaTeX was in one- or two-column mode
+%    \begin{macrocode}
+local KEY_COLUMNCOUNT = 'columncount'
+%    \end{macrocode}
+% Boolean: true iff LaTeX is in twoside mode
+%    \begin{macrocode}
+local KEY_TWOSIDE = 'twoside'
+%    \end{macrocode}
+%
+%
+%
+% \subsubsection{Keys for item data tables}
+%
+% Integer: Used to identify data with item
+%    \begin{macrocode}
+local KEY_ITEMNO = 'itemno'
+%    \end{macrocode}
+% Integer: On-page number
+%    \begin{macrocode}
+local KEY_PAGENO = 'pageno'
+%    \end{macrocode}
+% Dimensions: \(x\) and \(y\) positions of call to \cs{marginalia}
+%    \begin{macrocode}
+local KEY_XPOS = 'xpos'
+local KEY_YPOS = 'ypos'
+%    \end{macrocode}
+% Dimensions: Height and depth of typeset item
+%    \begin{macrocode}
+local KEY_HEIGHT = 'height'
+local KEY_DEPTH = 'depth'
+%    \end{macrocode}
+% Integer: Specified type, following \luavar{TYPE_*}
+%    \begin{macrocode}
+local KEY_TYPE = 'type'
+%    \end{macrocode}
+% Integer: corresponds to value of \key{pos} key: \(0 = \texttt{auto}\), \(1 = \texttt{reverse}\), \(2 = \texttt{left}\),
+% \(3 = \texttt{right}\), \(4 = \texttt{nearest}\)
+%    \begin{macrocode}
+local KEY_POS = 'pos'
+%    \end{macrocode}
+% Integer: corresponds to value of \key{column} key: \(-1 = \texttt{auto}\), \(0 = \texttt{one}\), \(1 = \texttt{left}\),
+% \(2 = \texttt{right}\)
+%    \begin{macrocode}
+local KEY_COLUMN = 'column'
+%    \end{macrocode}
+% Dimension: specified vertical shift
+%    \begin{macrocode}
+local KEY_YSHIFT = 'yshift'
+%    \end{macrocode}
+% Dimensions: specified vertical separations
+%    \begin{macrocode}
+local KEY_YSEP_ABOVE = 'ysep above'
+local KEY_YSEP_BELOW = 'ysep below'
+local KEY_YSEP_PAGE_TOP = 'ysep page top'
+local KEY_YSEP_PAGE_BOTTOM = 'ysep page bottom'
+%    \end{macrocode}
+%
+% \medskip\noindent
+% The preceding keys refer to values that will be supplied from \LaTeX. The remaining values will be computed in Lua
+% and passed back to \LaTeX.
+%
+% \medskip\noindent
+% Integer: column in which the call to \cs{marginalia} was located: \(0 = \textrm{one-column}\),
+% \(1 = \textrm{left}\), \(2 = \textrm{right}\)
+%    \begin{macrocode}
+local KEY_COLNO_COMPUTED = 'colno computed'
+%    \end{macrocode}
+% Dimension: Horizontal shift between the call to \cs{marginalia} and the margin in which the item should be located
+%    \begin{macrocode}
+local KEY_XSHIFT_COMPUTED = 'xshift computed'
+%    \end{macrocode}
+% Dimension: Computed vertical shift
+%    \begin{macrocode}
+local KEY_YSHIFT_COMPUTED = 'yshift computed'
+%    \end{macrocode}
+% Integer: Side of text on which the item will appear: \(0 = \textrm{right}\), \(1 = \textrm{left}\)
+%    \begin{macrocode}
+local KEY_SIDE_COMPUTED = 'side computed'
+%    \end{macrocode}
+% Integer: Number of margin in which the item will appear, \(0 = \textrm{recto outer}\), \(1 = \textrm{recto inner}\),
+% \(2 = \textrm{verso outer}\), \(3 = \textrm{verso inner}\), \(4 = \textrm{ right between}\),
+% \(5 = \textrm{left between}\)
+%    \begin{macrocode}
+local KEY_MARGINNO_COMPUTED = 'marginno computed'
+%    \end{macrocode}
+% Boolean: Whether the item will actually appear on the page
+%    \begin{macrocode}
+local KEY_ENABLED_COMPUTED = 'enabled computed'
+%    \end{macrocode}
+%
+%
+%
+% \subsection{Utility functions}
+%
+% \begin{macro}[int]{list_filter}
+%   Take a list \luavar{t} and remove from it any elements for which the function
+%   \luavar{f} does not return true. (The index \luavar{j} is always the destination index to which a `keep' element
+%   is moved.)\sidenote{Code adapted from \url{https://stackoverflow.com/a/53038524/8990243}.}
+%    \begin{macrocode}
+local function list_filter(t, f)
+  local j = 1
+  local n = #t
+
+  for i=1,n do
+    if (f(t[i])) then
+      if (i ~= j) then
+        t[j] = t[i]
+        t[i] = nil
+      end
+      j = j + 1
+    else
+      t[i] = nil
+    end
+  end
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{list_filter}
+%   Return boolean true iff \luavar{s} is exactly the string `\luavar{true}'.
+%    \begin{macrocode}
+local function toboolean(s)
+  return s == "true"
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{get_data_page_number}
+%   Take a item or page data and return a human-readable string indicating the page to which the data pertains.
+%    \begin{macrocode}
+local function get_data_page_number(data)
+  local pageno = data[KEY_PAGENO]
+  if pageno ~= nil then
+    return 'p' .. pageno .. ' (' .. data[KEY_ABSPAGENO] .. ')'
+  else
+    return data[KEY_ABSPAGENO]
+  end
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsection{Generic page/item data functions}
+%
+% \begin{macro}[int]{parse_data}
+% Parse \luavar{keyvalue_string} and return the corresponding data as a table. The \luavar{keyvalue_string} is
+% expected to be of precisely the kind written to the \file{.aux} file as the parameter of \cs{marginalia at pagedata} or
+% \cs{marginalia at notedata}.
+%
+% Ignore any keys in \luavar{keyvalue_string} that are not listed in \luavar{conversion_table}. Fill in any missing
+% value with values from \luavar{defaults_table}.
+%
+% \luavar{conversion_table} is indexed by possible keys, with values equal to functions to convert the corresponding
+% value string to the value that should appear in the returned table.
+%
+% \luavar{defaults_table} is indexed by keys that \emph{will} appear in the returned table, using the corresponding
+% value unless it was given in \luavar{keyvalue_string} and the key appeared in \luavar{conversion_table}.
+%    \begin{macrocode}
+local function parse_data(keyvalue_string,conversion_table,defaults_table)
+
+  local key
+  local value
+  local result = {}
+
+  for s in string.gmatch(keyvalue_string,'([^,]+)') do
+
+    key,value = string.match(s,'^(.+)=(.+)$')
+    local conv = conversion_table[key]
+    if conv ~= nil then
+      result[key] = conv(value)
+    end
+
+  end
+
+  for key,value in pairs(defaults_table) do
+    if not(result[key] ~= nil) then
+      result[key] = value
+    end
+  end
+
+  return result
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{check_data}
+%   Check \luavar{keyvalue_string} against stored data. If it is new or has changed, append a report to
+%   \luavar{report_table}. Set the \luavar{KEY_CHECKED} of the data item to true.
+%
+%   The \luavar{keyvalue_string} is processed using \luavar{conversion_table} and \luavar{defaults_table} as per the
+%   \luavar{parse_data} function. The resulting table is compared to the table in \luavar{data_table} with the same value
+%   whose key is \luavar{data_table_key}. The tables are compared using the fields indexed by keys in
+%   \luavar{conversion_table}.
+%    \begin{macrocode}
+local function check_data(keyvalue_string,conversion_table,defaults_table,
+                          data_table,data_table_key_field,report_table)
+
+  local new_data = parse_data(keyvalue_string,
+                              conversion_table,defaults_table)
+
+  local data_table_key = new_data[data_table_key_field]
+
+  local stored_data = data_table[data_table_key]
+  if stored_data == nil then
+    table.insert(
+      report_table,
+      get_data_page_number(new_data) .. ' New'
+    )
+  else
+    local change_report = ''
+    for k,_ in pairs(conversion_table) do
+      if stored_data[k] ~= new_data[k] then
+        change_report = change_report
+          .. ' ' .. k .. ':' ..
+          tostring(stored_data[k]) .. '->' .. tostring(new_data[k])
+      end
+    end
+    if change_report ~= '' then
+      table.insert(
+        report_table,
+        get_data_page_number(new_data) .. ' ' .. change_report
+      )
+    end
+    stored_data[KEY_CHECKED] = true
+  end
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{check_removed_data}
+%   Check whether data have been removed from \luavar{data_table}, which corresponds to some entry having the value
+%   of \luavar{KEY_CHECKED} being false. In this case, append a report to \luavar{report_table}.
+%    \begin{macrocode}
+local function check_removed_data(data_table,report_table)
+  for _,data in pairs(data_table) do
+    if not data[KEY_CHECKED] then
+      table.insert(
+        report_table,
+        ' Removed'
+      )
+      break
+    end
+  end
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsection{Processing of page data from \texorpdfstring{\file{.aux}}{.aux} file}
+%
+% Conversion and default tables.
+%    \begin{macrocode}
+local PAGE_DATA_CONVERSION_TABLE = {
+  [KEY_PAGEDATANO] = tonumber,
+  [KEY_ABSPAGENO] = tonumber,
+  [KEY_HOFFSETORIGIN] = tonumber,
+  [KEY_VOFFSETORIGIN] = tonumber,
+  [KEY_HOFFSET] = tonumber,
+  [KEY_VOFFSET] = tonumber,
+  [KEY_PAPERHEIGHT] = tonumber,
+  [KEY_ODDSIDEMARGIN] = tonumber,
+  [KEY_EVENSIDEMARGIN] = tonumber,
+  [KEY_COLUMNCOUNT] = tonumber,
+  [KEY_COLUMNWIDTH] = tonumber,
+  [KEY_COLUMNSEP] = tonumber,
+  [KEY_TEXTWIDTH] = tonumber,
+  [KEY_TWOSIDE] = toboolean,
+}
+local PAGE_DATA_DEFAULT_TABLE = {
+  [KEY_PAGEDATANO] = 0,
+  [KEY_ABSPAGENO] = 0,
+  [KEY_HOFFSETORIGIN] = tex.sp('1in'),
+  [KEY_VOFFSETORIGIN] = tex.sp('1in'),
+  [KEY_HOFFSET] = tex.dimen['hoffset'],
+  [KEY_VOFFSET] = tex.dimen['voffset'],
+  [KEY_PAPERHEIGHT] = tex.dimen['paperheight'],
+  [KEY_ODDSIDEMARGIN] = tex.dimen['oddsidemargin'],
+  [KEY_EVENSIDEMARGIN] = tex.dimen['evensidemargin'],
+  [KEY_TEXTWIDTH] = tex.dimen['textwidth'],
+  [KEY_COLUMNWIDTH] = tex.dimen['columnwidth'],
+  [KEY_COLUMNSEP] = tex.dimen['columnsep'],
+  [KEY_COLUMNCOUNT] = 1,
+  [KEY_TWOSIDE] = false,
+  [KEY_CHECKED] = false,
+}
+%    \end{macrocode}
+%
+% \begin{macro}[int]{store_page_data}
+%   Store page data supplied by \luavar{keyvalue_string} in \luavar{PAGE_DATA_MAIN_TABLE}.
+%    \begin{macrocode}
+local function store_page_data(keyvalue_string)
+
+  local page_data = parse_data(keyvalue_string,
+                               PAGE_DATA_CONVERSION_TABLE,
+                               PAGE_DATA_DEFAULT_TABLE)
+
+  PAGE_DATA_MAIN_TABLE[page_data[KEY_PAGEDATANO]] = page_data
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{store_default_page_data}
+%   Store default page data in \luavar{PAGE_DATA_MAIN_TABLE}, so that there is some data to work with when
+%   computing item positions, even on a first run, when no page data has been written to the \file{.aux} file.
+%    \begin{macrocode}
+local function store_default_page_data()
+
+  default_page_data = parse_data('',
+                                 PAGE_DATA_CONVERSION_TABLE,
+                                 PAGE_DATA_DEFAULT_TABLE)
+
+  default_page_data[KEY_ABSPAGENO] = 1
+  default_page_data[KEY_CHECKED] = true
+
+  PAGE_DATA_MAIN_TABLE[0] = default_page_data
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{check_page_data}
+% Check whether page_data supplied by keyvalue_string differs from that in \luavar{PAGE_DATA_MAIN_TABLE}, appending
+% reports to \luavar{PAGE_CHANGE_REPORT_TABLE} if so.
+%    \begin{macrocode}
+local function check_page_data(keyvalue_string)
+
+  check_data(keyvalue_string,
+             PAGE_DATA_CONVERSION_TABLE,PAGE_DATA_DEFAULT_TABLE,
+             PAGE_DATA_MAIN_TABLE,KEY_PAGEDATANO,
+             PAGE_CHANGE_REPORT_TABLE)
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsection{Processing of item data from \texorpdfstring{\file{.aux}}{.aux} file}
+%
+% Conversion and default tables.
+%    \begin{macrocode}
+local ITEM_DATA_CONVERSIONS = {
+  [KEY_ITEMNO] = tonumber,
+  [KEY_ABSPAGENO] = tonumber,
+  [KEY_PAGENO] = tonumber,
+  [KEY_XPOS] = tonumber,
+  [KEY_YPOS] = tonumber,
+  [KEY_HEIGHT] = tonumber,
+  [KEY_DEPTH] = tonumber,
+  [KEY_TYPE] = tonumber,
+  [KEY_POS] = tonumber,
+  [KEY_COLUMN] = tonumber,
+  [KEY_YSHIFT] = tonumber,
+  [KEY_YSEP_ABOVE] = tonumber,
+  [KEY_YSEP_BELOW] = tonumber,
+  [KEY_YSEP_PAGE_TOP] = tonumber,
+  [KEY_YSEP_PAGE_BOTTOM] = tonumber,
+  [KEY_CHECKED] = toboolean,
+}
+local ITEM_DATA_DEFAULTS = {
+  [KEY_ITEMNO] = 0,
+  [KEY_ABSPAGENO] = 1,
+  [KEY_PAGENO] = 1,
+  [KEY_XPOS] = 0,
+  [KEY_YPOS] = 0,
+  [KEY_HEIGHT] = 0,
+  [KEY_DEPTH] = 0,
+  [KEY_TYPE] = 0,
+  [KEY_POS] = 0,
+  [KEY_COLUMN] = -1,
+  [KEY_YSHIFT] = 0,
+  [KEY_YSEP_ABOVE] = tex.dimen['marginparpush'],
+  [KEY_YSEP_BELOW] = tex.dimen['marginparpush'],
+  [KEY_YSEP_PAGE_TOP] = tex.dimen['marginparpush'],
+  [KEY_YSEP_PAGE_BOTTOM] = tex.dimen['marginparpush'],
+  [KEY_COLNO_COMPUTED] = 0,
+  [KEY_XSHIFT_COMPUTED] = 0,
+  [KEY_YSHIFT_COMPUTED] = 0,
+  [KEY_SIDE_COMPUTED] = 0,
+  [KEY_MARGINNO_COMPUTED] = 0,
+  [KEY_ENABLED_COMPUTED] = true,
+  [KEY_CHECKED] = false,
+}
+%    \end{macrocode}
+% \luavar{ITEM_DATA_DEFAULTS} is also used by \luafunc{load_item_data} when no stored item data is found in
+% \luavar{ITEM_DATA_MAIN_TABLE}.
+
+% \begin{macro}[int]{store_item_data}
+%   Store item_data supplied by \luavar{keyvalue_string} in \luavar{ITEM_DATA_MAIN_TABLE}.
+%    \begin{macrocode}
+local function store_item_data(keyvalue_string)
+
+  local item = parse_data(keyvalue_string,
+                          ITEM_DATA_CONVERSIONS,
+                          ITEM_DATA_DEFAULTS)
+
+  ITEM_DATA_MAIN_TABLE[item[KEY_ITEMNO]] = item
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{check_item_data}
+%   Check whether item_data supplied by \luavar{keyvalue_string} differs from that in \luavar{ITEM_DATA_MAIN_TABLE},
+%   appending reports to \luavar{ITEM_CHANGE_REPORT_TABLE} if so.
+%    \begin{macrocode}
+local function check_item_data(keyvalue_string)
+
+  check_data(keyvalue_string,
+             ITEM_DATA_CONVERSIONS,ITEM_DATA_DEFAULTS,
+             ITEM_DATA_MAIN_TABLE,KEY_ITEMNO,
+             ITEM_CHANGE_REPORT_TABLE)
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsection{Writing reports}
+%
+% \begin{macro}[int]{write_report}
+%   Write the data contained in \luavar{report_table} to \TeX\ in a format suitable for a package warning. The written
+%   text will contain at most \luavar{max_length} items.
+%    \begin{macrocode}
+local function write_report(report_table,max_length)
+
+  if #report_table > 0 then
+    local report_text
+    local report_length
+
+    if #report_table <= max_length then
+      report_length = #report_table
+      report_text = ' Here they are:\n'
+    else
+      report_length = max_length
+      report_text = ' Here are the first ' .. report_length .. ':\n'
+    end
+
+    for i=1,report_length do
+      report_text = report_text .. report_table[i]
+      if i < report_length then
+        report_text = report_text .. '\n'
+      end
+    end
+
+    tex.print(report_text)
+  end
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{write_problem_report}
+% Write a report about placement problems to \TeX\ in a format suitable for a package warning.
+%    \begin{macrocode}
+local function write_problem_report()
+
+  write_report(PROBLEM_REPORT_TABLE,PROBLEM_REPORT_MAX_LENGTH)
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{write_item_change_report}
+%   Write a report about changes in item data to \TeX\ in a format suitable for a package warning.
+%    \begin{macrocode}
+local function write_item_change_report()
+
+  check_removed_data(ITEM_DATA_MAIN_TABLE,ITEM_CHANGE_REPORT_TABLE)
+  write_report(ITEM_CHANGE_REPORT_TABLE,ITEM_CHANGE_REPORT_MAX_LENGTH)
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{write_page_change_report}
+%   Write a report about changes in page data to \TeX\ in a format suitable for a package warning.
+%    \begin{macrocode}
+local function write_page_change_report()
+
+  check_removed_data(PAGE_DATA_MAIN_TABLE,PAGE_CHANGE_REPORT_TABLE)
+  write_report(PAGE_CHANGE_REPORT_TABLE,PAGE_CHANGE_REPORT_MAX_LENGTH)
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsection{Computing horizontal positions}
+%
+% It is necessary to determine whether an item should be placed on the right or left of the text block, and in which
+% column it lies. The following lookup tables are used.
+%
+% The value found in \luavar{RIGHTSIDE_LOOKUP_TABLE} is either \luavar{true} (right) or \luavar{false} (left). It is
+% indexed by whether the item is on a recto page (\luavar{true}/\luavar{false}), whether it pertains to single-column
+% text, the left column, or the right colum (\luavar{0}/\luavar{1}/\luavar{2}), and the value of \key{pos} being
+% either \val{auto} or \val{reverse}.
+%    \begin{macrocode}
+local RIGHTSIDE_LOOKUP_TABLE = {
+  [true] = {
+    [0] = {
+      [POS_AUTO] = true,
+      [POS_REVERSE] = false,
+    },
+    [1] = {
+      [POS_AUTO] = false,
+      [POS_REVERSE] = true,
+    },
+    [2] = {
+      [POS_AUTO] = true,
+      [POS_REVERSE] = false,
+    },
+  },
+  [false] = {
+    [0] = {
+      [POS_AUTO] = false,
+      [POS_REVERSE] = true,
+    },
+    [1] = {
+      [POS_AUTO] = true,
+      [POS_REVERSE] = false,
+    },
+    [2] = {
+      [POS_AUTO] = false,
+      [POS_REVERSE] = true,
+    },
+  },
+}
+%    \end{macrocode}
+% The value found in \luavar{MARGINNO_LOOKUP_TABLE} ranges from \luavar{0} to \luavar{5} (see
+% \luavar{KEY_MARGINNO_COMPUTED} for the meaning of these values). It is indexed by whether the item is on a recto
+% page (\luavar{true}/\luavar{false}), whether it pertains to single-column text, the left column, or the right colum
+% (\luavar{0}/\luavar{1}/\luavar{2}), and whether it is to be placed on the right of the text block
+% (\luavar{true}/\luavar{false}).
+%    \begin{macrocode}
+local MARGINNO_LOOKUP_TABLE = {
+  [true] = {
+    [0] = {
+      [false] = 1,
+      [true] = 0,
+    },
+    [1] = {
+      [false] = 1,
+      [true] = 5,
+    },
+    [2] = {
+      [false] = 4,
+      [true] = 0,
+    },
+  },
+  [false] = {
+    [0] = {
+      [false] = 2,
+      [true] = 3,
+    },
+    [1] = {
+      [false] = 2,
+      [true] = 5,
+    },
+    [2] = {
+      [false] = 4,
+      [true] = 3,
+    },
+  },
+}
+%    \end{macrocode}
+%
+% \begin{macro}[int]{compute_items_horizontal}
+%   For every \luavar{item_data} in \luavar{item_data_list}, compute the fields relevant to horizontal positioning,
+%   namely \luavar{KEY_COLNO_COMPUTED}, \luavar{KEY_XSHIFT_COMPUTED}, \luavar{KEY_SIDE_COMPUTED}, based on the layout
+%   information in page_data. Every item described in \luavar{item_data_list} is assumed to be on the same page.
+%    \begin{macrocode}
+local function compute_items_horizontal(item_data_list,page_data)
+%    \end{macrocode}
+%   Immediately return if \luavar{item_data_list} is empty, to avoid edge cases.
+%    \begin{macrocode}
+  if #item_data_list == 0 then
+    return
+  end
+%    \end{macrocode}
+%   Information used frequently and which is the same for every item.
+%    \begin{macrocode}
+  local pageno = item_data_list[1][KEY_PAGENO]
+  local twoside = page_data[KEY_TWOSIDE]
+  local recto = ((pageno % 2) == 1) or (not twoside)
+  local columncount = page_data[KEY_COLUMNCOUNT]
+%    \end{macrocode}
+%   Tables to contain the \(x\)-coordinates of left edge, right edge, and middle of the current text, whether a single
+%   column (index 0), the left column (index 1), or the right column (index 2).
+%    \begin{macrocode}
+  local x_textleft = {}
+  local x_textright = {}
+  local x_textmiddle = {}
+%    \end{macrocode}
+%   First, compute necessary dimensions for single-column text, since most of these calculations would be used anyway
+%   for two-column text. The terms used in calculating \luavar{x_textleft[0]} respectively take one to the origin of
+%   \cs{hoffset}, to the origin of \cs{oddsidemargin} and \cs{evensidemargin}, and to the left-hand side of the text
+%   block.
+%    \begin{macrocode}
+  if recto then
+    x_textleft[0] = (
+      page_data[KEY_HOFFSETORIGIN]
+      + page_data[KEY_HOFFSET]
+      + page_data[KEY_ODDSIDEMARGIN]
+    )
+    x_textright[0] = (
+      x_textleft[0]
+      + page_data[KEY_TEXTWIDTH]
+    )
+  else
+    x_textleft[0] = (
+      page_data[KEY_HOFFSETORIGIN]
+      + page_data[KEY_HOFFSET]
+      + page_data[KEY_EVENSIDEMARGIN]
+    )
+    x_textright[0] = (
+      x_textleft[0]
+      + page_data[KEY_TEXTWIDTH]
+    )
+  end
+  x_textmiddle[0] = (x_textleft[0] + x_textright[0])/2
+
+
+  if columncount == 1 then
+%    \end{macrocode}
+%    If the page is one-column, the field \luavar{KEY_COLNO_COMPUTED} can be set immediately for every item_data.
+%    \begin{macrocode}
+    for i=1,#item_data_list do
+      item_data_list[i][KEY_COLNO_COMPUTED] = 0
+    end
+  else
+%    \end{macrocode}
+%    If the page is two-column, calculate the \(x\)-coordinates of the left and right edges and the mid-point of each
+%    column.
+%    \begin{macrocode}
+    x_textleft[1] = x_textleft[0]
+    x_textright[1] = (
+      x_textleft[1]
+      + page_data[KEY_COLUMNWIDTH]
+    )
+    x_textmiddle[1] = (x_textleft[1] + x_textright[1])/2
+
+    x_textleft[2] = (
+      x_textright[1]
+      + page_data[KEY_COLUMNSEP]
+    )
+    x_textright[2] = (
+      x_textleft[2]
+      + page_data[KEY_COLUMNWIDTH]
+    )
+    x_textmiddle[2] = (x_textleft[2] + x_textright[2])/2
+
+%    \end{macrocode}
+%   Calculate the cut-off (mid-way between the columns) that distinguishes items from left and right columns.
+%    \begin{macrocode}
+    local left_column_x_limit = (
+      x_textright[1]
+      + .5*page_data[KEY_COLUMNSEP]
+    )
+%    \end{macrocode}
+%   Now set the field \luavar{KEY_COLNO_COMPUTED} for each item.
+%    \begin{macrocode}
+    for i=1,#item_data_list do
+      local item_data = item_data_list[i]
+
+      if item_data[KEY_COLUMN] >= 0 then
+        item_data[KEY_COLNO_COMPUTED] = item_data[KEY_COLUMN]
+      else
+        if item_data[KEY_XPOS] <= left_column_x_limit then
+          item_data[KEY_COLNO_COMPUTED] = 1
+        else
+          item_data[KEY_COLNO_COMPUTED] = 2
+        end
+      end
+    end
+
+  end
+%    \end{macrocode}
+%   For every item_data in item_data_list, compute and set the fields \luavar{KEY_SIDE_COMPUTED},
+%   \luavar{KEY_XSHIFT_COMPUTED}, and \luavar{KEY_MARGINNO_COMPUTED}.
+%    \begin{macrocode}
+  for i=1,#item_data_list do
+    local item = item_data_list[i]
+
+    local pos = item[KEY_POS]
+    local colnocomputed = item[KEY_COLNO_COMPUTED]
+
+    if pos == POS_LEFT then
+      rightside = false
+    elseif pos == POS_RIGHT then
+      rightside = true
+    elseif pos == POS_NEAREST then
+      rightside = (item[KEY_XPOS] >= x_textmiddle[colnocomputed])
+    else
+%    \end{macrocode}
+%   \luavar{pos} must be POS_AUTO or POS_REVERSE
+%    \begin{macrocode}
+      rightside = RIGHTSIDE_LOOKUP_TABLE[recto][colnocomputed][pos]
+    end
+
+    local marginno = MARGINNO_LOOKUP_TABLE[recto][colnocomputed][rightside]
+
+    if rightside then
+      item[KEY_SIDE_COMPUTED] = 0
+      item[KEY_XSHIFT_COMPUTED] = -item[KEY_XPOS]
+                                  + x_textright[colnocomputed]
+    else
+      item[KEY_SIDE_COMPUTED] = 1
+      item[KEY_XSHIFT_COMPUTED] = -item[KEY_XPOS]
+                                  + x_textleft[colnocomputed]
+    end
+    item[KEY_MARGINNO_COMPUTED] = marginno
+
+  end
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{get_y_item_top}
+%   Return the \(y\)-coordinate of the top of the item described by \luavar{item_data}.
+%    \begin{macrocode}
+local function get_y_item_top(item_data)
+  return item_data[KEY_YPOS]
+         + item_data[KEY_YSHIFT_COMPUTED]
+         + item_data[KEY_HEIGHT]
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{get_y_item_bottom}
+%   Return the \(y\)-coordinate of the bottom of the item described by \luavar{item_data}.
+%    \begin{macrocode}
+local function get_y_item_bottom(item_data)
+  return item_data[KEY_YPOS]
+         - item_data[KEY_DEPTH]
+         + item_data[KEY_YSHIFT_COMPUTED]
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{get_ysep_list}
+%   Calculate the separation to be used between adjacent marginal content items as described in
+%   \luavar{item_data_list}. The list is assumed to be sorted so that items are in the order they should appear on the
+%   page, top to bottom.
+%
+%   The idea is that we have the following arrangement for \(i = 1,\ldots,\luavar{\#item_data_list}\):
+%   {
+%     \null~~~~~~\(\vdots\)\\
+%     \null~~~~\luavar{item_data_list[i]}\\
+%     \null~~~~~~\luavar{ysep_list[i]}\\
+%     \null~~~~\luavar{item_data_list[i+1]}\\
+%     \null~~~~~~\(\vdots\)\\
+%   }
+%   Also set \luavar{ysep_list[0]} and \luavar{ysep_list[\#item_data_list]} to 0, to avoid checking when these values
+%   are accessed (although they are not used).
+%    \begin{macrocode}
+local function get_ysep_list(item_data_list)
+
+  local ysep_list = {}
+
+  ysep_list[0] = 0
+  for i=1,#item_data_list-1 do
+    ysep_list[i] = math.max(
+                     item_data_list[i][KEY_YSEP_BELOW],
+                     item_data_list[i+1][KEY_YSEP_ABOVE]
+                   )
+  end
+  ysep_list[#item_data_list] = 0
+
+  return ysep_list
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsection{Computing vertical positions}
+%
+%
+%
+% \subsubsection{Computing \val{optfixed} enabled}
+%
+% \begin{macro}[int]{compute_items_vertical_optfixed_enabled}
+%   For every \luavar{item_data} in \luavar{item_data_list} describing an item of type \luavar{TYPE_OPTFIXED}, check
+%   for a clash with an item of type \luavar{TYPE_FIXED}. If so, set \luavar{item_data[KEY_ENABLED_COMPUTED]} to
+%   \luavar{false}. Every item described in \luavar{item_data_list} is assumed to be on the same page and to have
+%   \luavar{KEY_YSHIFT} set to the default.
+%    \begin{macrocode}
+local function compute_items_vertical_optfixed_enabled(item_data_list)
+
+  local optfixed_item_data_list = {}
+  local fixed_item_data_list = {}
+
+  for _,item_data in pairs(item_data_list) do
+    if item_data[KEY_TYPE] == TYPE_OPTFIXED then
+      optfixed_item_data_list[#optfixed_item_data_list+1] = item_data
+    elseif item_data[KEY_TYPE] == TYPE_FIXED then
+      fixed_item_data_list[#fixed_item_data_list+1] = item_data
+    end
+  end
+
+  for _,optfixed_item_data in pairs(optfixed_item_data_list) do
+    local optfixed_y_item_top = get_y_item_top(optfixed_item_data)
+    local optfixed_y_item_bottom = get_y_item_bottom(optfixed_item_data)
+
+    for _,fixed_item_data in pairs(fixed_item_data_list) do
+      local fixed_y_item_top = get_y_item_top(fixed_item_data)
+      local fixed_y_item_bottom = get_y_item_bottom(fixed_item_data)
+
+      if (
+        (
+          (fixed_y_item_bottom - optfixed_y_item_top)
+          <
+          math.max(
+            fixed_item_data[KEY_YSEP_BELOW],
+            optfixed_item_data[KEY_YSEP_ABOVE]
+          )
+        )
+        and
+        (
+          (optfixed_y_item_bottom - fixed_y_item_top)
+          <
+          math.max(
+            optfixed_item_data[KEY_YSEP_BELOW],
+            fixed_item_data[KEY_YSEP_ABOVE]
+          )
+        )
+      ) then
+        optfixed_item_data[KEY_ENABLED_COMPUTED] = false
+        break
+      end
+    end
+  end
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsubsection{Computing vertical adjustment}
+%
+% \begin{macro}[int]{compute_items_vertical_adjustment}
+%   For every \luavar{item_data} in \luavar{item_data_list}, compute the field relevant to vertical positioning,
+%   namely \luavar{KEY_YSHIFT_COMPUTED}, based on the layout information in \luavar{page_data}. Every item described
+%   in \luavar{item_data_list} is assumed to be on the same page and to have \luavar{KEY_YSHIFT} set to the default,
+%   and the list is assumed to be sorted so that items are in the order they should appear on the page, top to bottom.
+%    \begin{macrocode}
+local function compute_items_vertical_adjustment(item_data_list,page_data)
+%    \end{macrocode}
+%   Immediately return if \luavar{item_data_list} is empty, to avoid edge cases
+%    \begin{macrocode}
+  if #item_data_list == 0 then
+    return
+  end
+
+  local ysep_list = get_ysep_list(item_data_list)
+%    \end{macrocode}
+%   \textit{First pass of computation (downward).} \luavar{y_limit_above} will always be the highest \(y\)-coordinate
+%   at which the top of next item below can appear.
+%    \begin{macrocode}
+  local y_limit_above = (
+    page_data[KEY_VOFFSET]
+    + page_data[KEY_PAPERHEIGHT]
+    - item_data_list[1][KEY_YSEP_PAGE_TOP]
+  )
+
+  for i=1,#item_data_list do
+    local item_data = item_data_list[i]
+
+    local y_item_top = get_y_item_top(item_data)
+
+    if y_item_top > y_limit_above then
+      if item_data[KEY_TYPE] == TYPE_NORMAL then
+        item_data[KEY_YSHIFT_COMPUTED] = item_data[KEY_YSHIFT_COMPUTED]
+                                         + (y_limit_above - y_item_top)
+      end
+    end
+
+    y_limit_above = get_y_item_bottom(item_data) - ysep_list[i]
+  end
+%    \end{macrocode}
+%   \textit{Second pass of computation (upward)}. \luavar{y_limit_below} will always be the lowest \(y\)-coordinate at
+%   which the bottom of next item above can appear.
+%    \begin{macrocode}
+  local y_limit_below = (
+    page_data[KEY_VOFFSET]
+    + item_data_list[#item_data_list][KEY_YSEP_PAGE_BOTTOM]
+  )
+
+  for i=#item_data_list,1,-1 do
+    local item_data = item_data_list[i]
+
+    local y_item_bottom = get_y_item_bottom(item_data)
+
+    if y_item_bottom < y_limit_below then
+      if item_data[KEY_TYPE] == TYPE_NORMAL then
+        item_data[KEY_YSHIFT_COMPUTED] = item_data[KEY_YSHIFT_COMPUTED]
+                                         + (y_limit_below - y_item_bottom)
+      end
+    end
+
+    y_limit_below = get_y_item_top(item_data) + ysep_list[i-1]
+  end
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsubsection{Checking vertical adjustment}
+%
+% Messages to use when checking results of vertical adjustment.
+%    \begin{macrocode}
+local ITEM_PASSED_YSEP_PAGE_TOP_MESSAGES = {
+  [TYPE_NORMAL] = 'Moveable item > ysep page top',
+  [TYPE_FIXED] = 'Topmost fixed item > ysep page top',
+  [TYPE_OPTFIXED] = 'Topmost optfixed item > ysep page top',
+}
+local ITEM_CLASH_MESSAGES = {
+  [TYPE_NORMAL] = {
+    [TYPE_NORMAL] = 'moveable items'
+                    .. ' (this shouldn\'t happen)',
+    [TYPE_FIXED] = 'moveable item above fixed item',
+    [TYPE_OPTFIXED] = 'moveable item above optfixed item',
+  },
+  [TYPE_FIXED] = {
+    [TYPE_NORMAL] = 'moveable item below fixed item',
+    [TYPE_FIXED] = 'fixed items',
+    [TYPE_OPTFIXED] = 'fixed item above optfixed item '
+                      .. '(this shouldn\'t happen)',
+  },
+  [TYPE_OPTFIXED] = {
+    [TYPE_NORMAL] = 'moveable items below optfixed item',
+    [TYPE_FIXED] = 'fixed item below optfixed item '
+                   .. '(this shouldn\'t happen)',
+    [TYPE_OPTFIXED] = 'optfixed items '
+                      .. '(this shouldn\'t happen)',
+  },
+}
+local ITEM_PASSED_YSEP_PAGE_BOTTOM_MESSAGE = {
+  [TYPE_NORMAL] = 'Moveable item < ysep page bottom',
+  [TYPE_FIXED] = 'Bottommost fixed item < ysep page bottom',
+  [TYPE_OPTFIXED] = 'Bottommost optfixed item < ysep page bottom',
+}
+%    \end{macrocode}
+%
+% \begin{macro}[int]{check_items_vertical}
+%   For the items described by the item_data in \luavar{item_data_list}, check whether any clash or fail to obey
+%   \key{ysep page top} or \key{ysep page bottom}. If so, write messages to \luavar{PROBLEM_REPORT_TABLE}.
+%    \begin{macrocode}
+local function check_items_vertical(item_data_list,page_data)
+%    \end{macrocode}
+%   Immediately return if item_data_list is empty, to avoid edge cases
+%    \begin{macrocode}
+  if (#item_data_list) == 0 then
+    return
+  end
+
+  local ysep_list = get_ysep_list(item_data_list)
+
+  local item_data
+
+%    \end{macrocode}
+%   If any item fails to obey \key{ysep page top}, the first one in the list does.
+%    \begin{macrocode}
+  item_data = item_data_list[1]
+  if (
+       get_y_item_top(item_data) > page_data[KEY_VOFFSET]
+                                 + page_data[KEY_PAPERHEIGHT]
+                                 - item_data[KEY_YSEP_PAGE_TOP]
+  ) then
+    table.insert(
+      PROBLEM_REPORT_TABLE,
+      get_data_page_number(item_data)
+      .. ' ' .. ITEM_PASSED_YSEP_PAGE_TOP_MESSAGES[item_data[KEY_TYPE]]
+    )
+  end
+
+  for i=2,#item_data_list do
+    local item_data = item_data_list[i]
+    local prev_item_data = item_data_list[i-1]
+    if (
+      get_y_item_top(item_data) > get_y_item_bottom(prev_item_data)
+                                  - ysep_list[i-1]
+    ) then
+      table.insert(
+        PROBLEM_REPORT_TABLE,
+        get_data_page_number(item_data)
+        .. ' Clash: ' ..
+        ITEM_CLASH_MESSAGES[prev_item_data[KEY_TYPE]][item_data[KEY_TYPE]]
+      )
+    end
+  end
+%    \end{macrocode}
+%   If any item fails to obey \key{ysep page bottom}, the last one in the list does.
+%    \begin{macrocode}
+  item_data = item_data_list[#item_data_list]
+  if (
+    get_y_item_bottom(item_data) < page_data[KEY_VOFFSET]
+                                   + item_data[KEY_YSEP_PAGE_BOTTOM]
+  ) then
+    table.insert(
+      PROBLEM_REPORT_TABLE,
+      get_data_page_number(item_data)
+      .. ' ' .. ITEM_PASSED_YSEP_PAGE_BOTTOM_MESSAGE[item_data[KEY_TYPE]]
+    )
+  end
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsubsection{Core vertical position computation}
+%
+% \begin{macro}[int]{compute_items_vertical}
+%   For every \luavar{item_data} in \luavar{item_data_list}, compute the field relevant to vertical positioning,
+%   namely \luavar{KEY_YSHIFT_COMPUTED}, based on the layout information in \luavar{page_data}. This may involve
+%   setting the field \luavar{KEY_ENABLED_COMPUTED} to false. In such a case, the relevant item_data is removed from
+%   \luavar{item_data_list}.
+%    \begin{macrocode}
+local function compute_items_vertical(item_data_list,page_data)
+%    \end{macrocode}
+%   Set \luavar{KEY_YSHIFT_COMPUTED} of each \luavar{item_data} to the user-supplied value.
+%    \begin{macrocode}
+  for i=1,#item_data_list do
+    local item_data = item_data_list[i]
+
+    item_data[KEY_YSHIFT_COMPUTED] = item_data[KEY_YSHIFT]
+  end
+%    \end{macrocode}
+%   Decide which items of type \luavar{ITEM_DATA_OPTFIXED} are to be disabled.
+%    \begin{macrocode}
+  compute_items_vertical_optfixed_enabled(item_data_list)
+%    \end{macrocode}
+%   Strip any \luavar{item_data} with \luavar{KEY_ENABLED_COMPUTED} set to false from \luavar{item_data_list}.
+%    \begin{macrocode}
+  list_filter(item_data_list,function(item_data)
+    return item_data[KEY_ENABLED_COMPUTED]
+  end)
+%    \end{macrocode}
+%   Sort \luavar{item_data_list} according to the stored position from top to bottom and left to right on the page,
+%   resolving ties using \luavar{KEY_ITEMNO}.
+%    \begin{macrocode}
+  table.sort(
+    item_data_list,
+    function(left,right)
+      local y_diff = left[KEY_YPOS] - right[KEY_YPOS]
+
+      if y_diff > 0 then
+        return true
+      elseif y_diff < 0 then
+        return false
+      end
+
+      local x_diff = left[KEY_XPOS] - right[KEY_XPOS]
+
+      if x_diff < 0 then
+        return true
+      elseif x_diff > 0 then
+        return false
+      end
+
+      return (left[KEY_ITEMNO] < right[KEY_ITEMNO])
+    end
+  )
+
+  compute_items_vertical_adjustment(item_data_list,page_data)
+
+  check_items_vertical(item_data_list,page_data)
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{compute_items}
+%   For every item represented in \luavar{ITEM_DATA_MAIN_TABLE}, use the \luavar{page_data} stored in
+%   \luavar{PAGE_DATA_MAIN_TABLE} to compute the item_data values necessary to place the item correctly on the page,
+%   namely those indexed by: \luavar{KEY_COLNO_COMPUTED}, \luavar{KEY_XSHIFT_COMPUTED}, \luavar{KEY_YSHIFT_COMPUTED},
+%   \luavar{KEY_SIDE_COMPUTED}, \luavar{KEY_ENABLED_COMPUTED}.
+%    \begin{macrocode}
+local function compute_items()
+%    \end{macrocode}
+%   Compute the maximum abspageno, which will be the last page of the document on which a item appears.
+%    \begin{macrocode}
+  local max_abspageno = 0
+
+  for k,v in pairs(ITEM_DATA_MAIN_TABLE) do
+    max_abspageno = math.max(v[KEY_ABSPAGENO],max_abspageno)
+  end
+%    \end{macrocode}
+%   \luavar{list per_abspage_item_data_list} will be a list indexed by absolute page numbers. Each entry will be a
+%   list (possibly empty) of \luavar{item_data} describing the items that appear on the corresponding page.
+%    \begin{macrocode}
+  local per_abspage_item_data_list = {}
+%    \end{macrocode}
+%   Prepare \luavar{per_abspage_item_data_list} by making each entry an empty list, then fill it from
+%   \luavar{ITEM_DATA_MAIN_TABLE}.
+%    \begin{macrocode}
+  for i=1,max_abspageno do
+    per_abspage_item_data_list[i] = {}
+  end
+  for _,item_data in pairs(ITEM_DATA_MAIN_TABLE) do
+    local temp_table = per_abspage_item_data_list[item_data[KEY_ABSPAGENO]]
+    temp_table[#temp_table+1] = item_data
+  end
+%    \end{macrocode}
+%   \luavar{per_abspage_item_data_list} will be a list indexed by abssolute page numbers. Each entry will be a
+%   \luavar{page_data} describing the corresponding page. Usually multiple entries will be the same
+%   \luavar{page_data}: in the loop, \luavar{pagedatano} will be the index of the last entry in
+%   \luavar{PAGE_DATA_MAIN_TABLE} with \luavar{KEY_ABSPAGENO} value less than or equal to \luavar{abspageno}. (There
+%   may be several such entries in \luavar{PAGE_DATA_MAIN_TABLE} because \cs{marginalianewgeometry} may have been
+%   called multiple times on the same page.) Note that \luavar{PAGE_DATA_MAIN_TABLE[0]} is available even if there was
+%   no data in the \file{.aux} file, because the defaults were stored by \luafunc{store_default_page_data}.
+%    \begin{macrocode}
+  local per_abspage_page_data_list = {}
+%    \end{macrocode}
+%    \begin{macrocode}
+  local pagedatano = 0
+  for abspageno = 1,max_abspageno do
+%    \end{macrocode}
+%    \begin{macrocode}
+    while (
+      PAGE_DATA_MAIN_TABLE[pagedatano+1] ~= nil
+      and
+      PAGE_DATA_MAIN_TABLE[pagedatano+1][KEY_ABSPAGENO] == abspageno
+    ) do
+      pagedatano = pagedatano+1
+    end
+    per_abspage_page_data_list[abspageno] = PAGE_DATA_MAIN_TABLE[pagedatano]
+  end
+%    \end{macrocode}
+%   Iterate through all pages and perform the necessary computations.
+%    \begin{macrocode}
+  for abspageno=1,#per_abspage_item_data_list do
+    local current_page_data = per_abspage_page_data_list[abspageno]
+    local current_page_item_data_list = per_abspage_item_data_list[abspageno]
+%    \end{macrocode}
+%     First, compute the horizontal positions, which includes sorting items into columns in two-column mode.
+%    \begin{macrocode}
+    compute_items_horizontal(current_page_item_data_list,current_page_data)
+%    \end{macrocode}
+%     Sort the items into sublists corresponding to the margins in which they are located.
+%    \begin{macrocode}
+    local current_page_item_data_sublists = {}
+
+    for i=0,5 do
+      current_page_item_data_sublists[i] = {}
+    end
+
+    for _,item_data in pairs(current_page_item_data_list) do
+      table.insert(
+        current_page_item_data_sublists[item_data[KEY_MARGINNO_COMPUTED]],
+        item_data
+      )
+    end
+%    \end{macrocode}
+%     Compute vertical positons for each sublist.
+%    \begin{macrocode}
+    for i=0,5 do
+      compute_items_vertical(
+        current_page_item_data_sublists[i],
+        current_page_data
+      )
+    end
+  end
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsection{Passing item_data back to \LaTeX}
+%
+% \begin{macro}[int]{load_item_data}
+%   Set the relevant \LaTeX\ counter and dimension variables to the values computed for \luavar{itemno}.
+%    \begin{macrocode}
+local function load_item_data(itemno)
+
+  item = ITEM_DATA_MAIN_TABLE[tonumber(itemno)]
+  if item == nil then
+    item = ITEM_DATA_DEFAULTS
+  end
+
+  tex.count['l__marginalia_page_int'] = item[KEY_PAGENO]
+  tex.count['l__marginalia_column_computed_int'] = item[KEY_COLNO_COMPUTED]
+  tex.dimen['l__marginalia_xshift_computed_dim'] = item[KEY_XSHIFT_COMPUTED]
+  tex.dimen['l__marginalia_yshift_computed_dim'] = item[KEY_YSHIFT_COMPUTED]
+  tex.count['l__marginalia_side_computed_int'] = item[KEY_SIDE_COMPUTED]
+  tex.count['l__marginalia_marginno_computed_int']
+    = item[KEY_MARGINNO_COMPUTED]
+  if item[KEY_ENABLED_COMPUTED] then
+    tex.count['l__marginalia_enabled_computed_int'] = 1
+  else
+    tex.count['l__marginalia_enabled_computed_int'] = 0
+  end
+
+end
+%    \end{macrocode}
+% \end{macro}
+%
+%
+%
+% \subsection{Export public functions}
+%
+% Finally, make available the functions that will be called from \LaTeX\ using \cs{lua_now:n} and \cs{lua_now:e}.
+%    \begin{macrocode}
+return {
+  store_default_page_data = store_default_page_data,
+  store_page_data = store_page_data,
+  check_page_data = check_page_data,
+
+  store_item_data = store_item_data,
+  check_item_data = check_item_data,
+
+  compute_items = compute_items,
+
+  load_item_data = load_item_data,
+
+  write_problem_report = write_problem_report,
+
+  write_page_change_report = write_page_change_report,
+  write_item_change_report = write_item_change_report,
+}
+%    \end{macrocode}
+%
+%
+%
+%    \begin{macrocode}
+%</lua>
+%    \end{macrocode}
+%
+%
+%
 % \clearpage
 % \end{implementation}

Modified: trunk/Master/texmf-dist/tex/lualatex/marginalia/marginalia.lua
===================================================================
--- trunk/Master/texmf-dist/tex/lualatex/marginalia/marginalia.lua	2025-02-18 21:06:58 UTC (rev 74102)
+++ trunk/Master/texmf-dist/tex/lualatex/marginalia/marginalia.lua	2025-02-18 21:07:08 UTC (rev 74103)
@@ -20,4 +20,809 @@
 -- and version 1.3c or later is part of all distributions of
 -- LaTeX version 2008-05-04 or later.
 -- 
-LUACODE
+local PAGE_DATA_MAIN_TABLE = {}
+local ITEM_DATA_MAIN_TABLE = {}
+local PROBLEM_REPORT_TABLE = {}
+local PAGE_CHANGE_REPORT_TABLE = {}
+local ITEM_CHANGE_REPORT_TABLE = {}
+local PROBLEM_REPORT_MAX_LENGTH = 40
+local PAGE_CHANGE_REPORT_MAX_LENGTH = 10
+local ITEM_CHANGE_REPORT_MAX_LENGTH = 10
+local TYPE_NORMAL = 1
+local TYPE_FIXED = 2
+local TYPE_OPTFIXED = 3
+local POS_AUTO = 1
+local POS_REVERSE = 2
+local POS_LEFT = 3
+local POS_RIGHT = 4
+local POS_NEAREST = 5
+local KEY_ABSPAGENO = 'abspageno'
+local KEY_CHECKED = 'checked'
+local KEY_PAGEDATANO = 'pagedatano'
+local KEY_HOFFSETORIGIN = 'hoffsetorigin'
+local KEY_VOFFSETORIGIN = 'voffsetorigin'
+local KEY_HOFFSET = 'hoffset'
+local KEY_VOFFSET = 'voffset'
+local KEY_PAPERHEIGHT = 'paperheight'
+local KEY_ODDSIDEMARGIN = 'oddsidemargin'
+local KEY_EVENSIDEMARGIN = 'evensidemargin'
+local KEY_TEXTWIDTH = 'textwidth'
+local KEY_COLUMNWIDTH = 'columnwidth'
+local KEY_COLUMNSEP = 'columnsep'
+local KEY_COLUMNCOUNT = 'columncount'
+local KEY_TWOSIDE = 'twoside'
+local KEY_ITEMNO = 'itemno'
+local KEY_PAGENO = 'pageno'
+local KEY_XPOS = 'xpos'
+local KEY_YPOS = 'ypos'
+local KEY_HEIGHT = 'height'
+local KEY_DEPTH = 'depth'
+local KEY_TYPE = 'type'
+local KEY_POS = 'pos'
+local KEY_COLUMN = 'column'
+local KEY_YSHIFT = 'yshift'
+local KEY_YSEP_ABOVE = 'ysep above'
+local KEY_YSEP_BELOW = 'ysep below'
+local KEY_YSEP_PAGE_TOP = 'ysep page top'
+local KEY_YSEP_PAGE_BOTTOM = 'ysep page bottom'
+local KEY_COLNO_COMPUTED = 'colno computed'
+local KEY_XSHIFT_COMPUTED = 'xshift computed'
+local KEY_YSHIFT_COMPUTED = 'yshift computed'
+local KEY_SIDE_COMPUTED = 'side computed'
+local KEY_MARGINNO_COMPUTED = 'marginno computed'
+local KEY_ENABLED_COMPUTED = 'enabled computed'
+local function list_filter(t, f)
+  local j = 1
+  local n = #t
+
+  for i=1,n do
+    if (f(t[i])) then
+      if (i ~= j) then
+        t[j] = t[i]
+        t[i] = nil
+      end
+      j = j + 1
+    else
+      t[i] = nil
+    end
+  end
+
+end
+local function toboolean(s)
+  return s == "true"
+end
+local function get_data_page_number(data)
+  local pageno = data[KEY_PAGENO]
+  if pageno ~= nil then
+    return 'p' .. pageno .. ' (' .. data[KEY_ABSPAGENO] .. ')'
+  else
+    return data[KEY_ABSPAGENO]
+  end
+end
+local function parse_data(keyvalue_string,conversion_table,defaults_table)
+
+  local key
+  local value
+  local result = {}
+
+  for s in string.gmatch(keyvalue_string,'([^,]+)') do
+
+    key,value = string.match(s,'^(.+)=(.+)$')
+    local conv = conversion_table[key]
+    if conv ~= nil then
+      result[key] = conv(value)
+    end
+
+  end
+
+  for key,value in pairs(defaults_table) do
+    if not(result[key] ~= nil) then
+      result[key] = value
+    end
+  end
+
+  return result
+
+end
+local function check_data(keyvalue_string,conversion_table,defaults_table,
+                          data_table,data_table_key_field,report_table)
+
+  local new_data = parse_data(keyvalue_string,
+                              conversion_table,defaults_table)
+
+  local data_table_key = new_data[data_table_key_field]
+
+  local stored_data = data_table[data_table_key]
+  if stored_data == nil then
+    table.insert(
+      report_table,
+      get_data_page_number(new_data) .. ' New'
+    )
+  else
+    local change_report = ''
+    for k,_ in pairs(conversion_table) do
+      if stored_data[k] ~= new_data[k] then
+        change_report = change_report
+          .. ' ' .. k .. ':' ..
+          tostring(stored_data[k]) .. '->' .. tostring(new_data[k])
+      end
+    end
+    if change_report ~= '' then
+      table.insert(
+        report_table,
+        get_data_page_number(new_data) .. ' ' .. change_report
+      )
+    end
+    stored_data[KEY_CHECKED] = true
+  end
+
+end
+local function check_removed_data(data_table,report_table)
+  for _,data in pairs(data_table) do
+    if not data[KEY_CHECKED] then
+      table.insert(
+        report_table,
+        ' Removed'
+      )
+      break
+    end
+  end
+end
+local PAGE_DATA_CONVERSION_TABLE = {
+  [KEY_PAGEDATANO] = tonumber,
+  [KEY_ABSPAGENO] = tonumber,
+  [KEY_HOFFSETORIGIN] = tonumber,
+  [KEY_VOFFSETORIGIN] = tonumber,
+  [KEY_HOFFSET] = tonumber,
+  [KEY_VOFFSET] = tonumber,
+  [KEY_PAPERHEIGHT] = tonumber,
+  [KEY_ODDSIDEMARGIN] = tonumber,
+  [KEY_EVENSIDEMARGIN] = tonumber,
+  [KEY_COLUMNCOUNT] = tonumber,
+  [KEY_COLUMNWIDTH] = tonumber,
+  [KEY_COLUMNSEP] = tonumber,
+  [KEY_TEXTWIDTH] = tonumber,
+  [KEY_TWOSIDE] = toboolean,
+}
+local PAGE_DATA_DEFAULT_TABLE = {
+  [KEY_PAGEDATANO] = 0,
+  [KEY_ABSPAGENO] = 0,
+  [KEY_HOFFSETORIGIN] = tex.sp('1in'),
+  [KEY_VOFFSETORIGIN] = tex.sp('1in'),
+  [KEY_HOFFSET] = tex.dimen['hoffset'],
+  [KEY_VOFFSET] = tex.dimen['voffset'],
+  [KEY_PAPERHEIGHT] = tex.dimen['paperheight'],
+  [KEY_ODDSIDEMARGIN] = tex.dimen['oddsidemargin'],
+  [KEY_EVENSIDEMARGIN] = tex.dimen['evensidemargin'],
+  [KEY_TEXTWIDTH] = tex.dimen['textwidth'],
+  [KEY_COLUMNWIDTH] = tex.dimen['columnwidth'],
+  [KEY_COLUMNSEP] = tex.dimen['columnsep'],
+  [KEY_COLUMNCOUNT] = 1,
+  [KEY_TWOSIDE] = false,
+  [KEY_CHECKED] = false,
+}
+local function store_page_data(keyvalue_string)
+
+  local page_data = parse_data(keyvalue_string,
+                               PAGE_DATA_CONVERSION_TABLE,
+                               PAGE_DATA_DEFAULT_TABLE)
+
+  PAGE_DATA_MAIN_TABLE[page_data[KEY_PAGEDATANO]] = page_data
+
+end
+local function store_default_page_data()
+
+  default_page_data = parse_data('',
+                                 PAGE_DATA_CONVERSION_TABLE,
+                                 PAGE_DATA_DEFAULT_TABLE)
+
+  default_page_data[KEY_ABSPAGENO] = 1
+  default_page_data[KEY_CHECKED] = true
+
+  PAGE_DATA_MAIN_TABLE[0] = default_page_data
+
+end
+local function check_page_data(keyvalue_string)
+
+  check_data(keyvalue_string,
+             PAGE_DATA_CONVERSION_TABLE,PAGE_DATA_DEFAULT_TABLE,
+             PAGE_DATA_MAIN_TABLE,KEY_PAGEDATANO,
+             PAGE_CHANGE_REPORT_TABLE)
+
+end
+local ITEM_DATA_CONVERSIONS = {
+  [KEY_ITEMNO] = tonumber,
+  [KEY_ABSPAGENO] = tonumber,
+  [KEY_PAGENO] = tonumber,
+  [KEY_XPOS] = tonumber,
+  [KEY_YPOS] = tonumber,
+  [KEY_HEIGHT] = tonumber,
+  [KEY_DEPTH] = tonumber,
+  [KEY_TYPE] = tonumber,
+  [KEY_POS] = tonumber,
+  [KEY_COLUMN] = tonumber,
+  [KEY_YSHIFT] = tonumber,
+  [KEY_YSEP_ABOVE] = tonumber,
+  [KEY_YSEP_BELOW] = tonumber,
+  [KEY_YSEP_PAGE_TOP] = tonumber,
+  [KEY_YSEP_PAGE_BOTTOM] = tonumber,
+  [KEY_CHECKED] = toboolean,
+}
+local ITEM_DATA_DEFAULTS = {
+  [KEY_ITEMNO] = 0,
+  [KEY_ABSPAGENO] = 1,
+  [KEY_PAGENO] = 1,
+  [KEY_XPOS] = 0,
+  [KEY_YPOS] = 0,
+  [KEY_HEIGHT] = 0,
+  [KEY_DEPTH] = 0,
+  [KEY_TYPE] = 0,
+  [KEY_POS] = 0,
+  [KEY_COLUMN] = -1,
+  [KEY_YSHIFT] = 0,
+  [KEY_YSEP_ABOVE] = tex.dimen['marginparpush'],
+  [KEY_YSEP_BELOW] = tex.dimen['marginparpush'],
+  [KEY_YSEP_PAGE_TOP] = tex.dimen['marginparpush'],
+  [KEY_YSEP_PAGE_BOTTOM] = tex.dimen['marginparpush'],
+  [KEY_COLNO_COMPUTED] = 0,
+  [KEY_XSHIFT_COMPUTED] = 0,
+  [KEY_YSHIFT_COMPUTED] = 0,
+  [KEY_SIDE_COMPUTED] = 0,
+  [KEY_MARGINNO_COMPUTED] = 0,
+  [KEY_ENABLED_COMPUTED] = true,
+  [KEY_CHECKED] = false,
+}
+
+local function store_item_data(keyvalue_string)
+
+  local item = parse_data(keyvalue_string,
+                          ITEM_DATA_CONVERSIONS,
+                          ITEM_DATA_DEFAULTS)
+
+  ITEM_DATA_MAIN_TABLE[item[KEY_ITEMNO]] = item
+
+end
+local function check_item_data(keyvalue_string)
+
+  check_data(keyvalue_string,
+             ITEM_DATA_CONVERSIONS,ITEM_DATA_DEFAULTS,
+             ITEM_DATA_MAIN_TABLE,KEY_ITEMNO,
+             ITEM_CHANGE_REPORT_TABLE)
+
+end
+local function write_report(report_table,max_length)
+
+  if #report_table > 0 then
+    local report_text
+    local report_length
+
+    if #report_table <= max_length then
+      report_length = #report_table
+      report_text = ' Here they are:\n'
+    else
+      report_length = max_length
+      report_text = ' Here are the first ' .. report_length .. ':\n'
+    end
+
+    for i=1,report_length do
+      report_text = report_text .. report_table[i]
+      if i < report_length then
+        report_text = report_text .. '\n'
+      end
+    end
+
+    tex.print(report_text)
+  end
+
+end
+local function write_problem_report()
+
+  write_report(PROBLEM_REPORT_TABLE,PROBLEM_REPORT_MAX_LENGTH)
+
+end
+local function write_item_change_report()
+
+  check_removed_data(ITEM_DATA_MAIN_TABLE,ITEM_CHANGE_REPORT_TABLE)
+  write_report(ITEM_CHANGE_REPORT_TABLE,ITEM_CHANGE_REPORT_MAX_LENGTH)
+
+end
+local function write_page_change_report()
+
+  check_removed_data(PAGE_DATA_MAIN_TABLE,PAGE_CHANGE_REPORT_TABLE)
+  write_report(PAGE_CHANGE_REPORT_TABLE,PAGE_CHANGE_REPORT_MAX_LENGTH)
+
+end
+local RIGHTSIDE_LOOKUP_TABLE = {
+  [true] = {
+    [0] = {
+      [POS_AUTO] = true,
+      [POS_REVERSE] = false,
+    },
+    [1] = {
+      [POS_AUTO] = false,
+      [POS_REVERSE] = true,
+    },
+    [2] = {
+      [POS_AUTO] = true,
+      [POS_REVERSE] = false,
+    },
+  },
+  [false] = {
+    [0] = {
+      [POS_AUTO] = false,
+      [POS_REVERSE] = true,
+    },
+    [1] = {
+      [POS_AUTO] = true,
+      [POS_REVERSE] = false,
+    },
+    [2] = {
+      [POS_AUTO] = false,
+      [POS_REVERSE] = true,
+    },
+  },
+}
+local MARGINNO_LOOKUP_TABLE = {
+  [true] = {
+    [0] = {
+      [false] = 1,
+      [true] = 0,
+    },
+    [1] = {
+      [false] = 1,
+      [true] = 5,
+    },
+    [2] = {
+      [false] = 4,
+      [true] = 0,
+    },
+  },
+  [false] = {
+    [0] = {
+      [false] = 2,
+      [true] = 3,
+    },
+    [1] = {
+      [false] = 2,
+      [true] = 5,
+    },
+    [2] = {
+      [false] = 4,
+      [true] = 3,
+    },
+  },
+}
+local function compute_items_horizontal(item_data_list,page_data)
+  if #item_data_list == 0 then
+    return
+  end
+  local pageno = item_data_list[1][KEY_PAGENO]
+  local twoside = page_data[KEY_TWOSIDE]
+  local recto = ((pageno % 2) == 1) or (not twoside)
+  local columncount = page_data[KEY_COLUMNCOUNT]
+  local x_textleft = {}
+  local x_textright = {}
+  local x_textmiddle = {}
+  if recto then
+    x_textleft[0] = (
+      page_data[KEY_HOFFSETORIGIN]
+      + page_data[KEY_HOFFSET]
+      + page_data[KEY_ODDSIDEMARGIN]
+    )
+    x_textright[0] = (
+      x_textleft[0]
+      + page_data[KEY_TEXTWIDTH]
+    )
+  else
+    x_textleft[0] = (
+      page_data[KEY_HOFFSETORIGIN]
+      + page_data[KEY_HOFFSET]
+      + page_data[KEY_EVENSIDEMARGIN]
+    )
+    x_textright[0] = (
+      x_textleft[0]
+      + page_data[KEY_TEXTWIDTH]
+    )
+  end
+  x_textmiddle[0] = (x_textleft[0] + x_textright[0])/2
+
+  if columncount == 1 then
+    for i=1,#item_data_list do
+      item_data_list[i][KEY_COLNO_COMPUTED] = 0
+    end
+  else
+    x_textleft[1] = x_textleft[0]
+    x_textright[1] = (
+      x_textleft[1]
+      + page_data[KEY_COLUMNWIDTH]
+    )
+    x_textmiddle[1] = (x_textleft[1] + x_textright[1])/2
+
+    x_textleft[2] = (
+      x_textright[1]
+      + page_data[KEY_COLUMNSEP]
+    )
+    x_textright[2] = (
+      x_textleft[2]
+      + page_data[KEY_COLUMNWIDTH]
+    )
+    x_textmiddle[2] = (x_textleft[2] + x_textright[2])/2
+
+    local left_column_x_limit = (
+      x_textright[1]
+      + .5*page_data[KEY_COLUMNSEP]
+    )
+    for i=1,#item_data_list do
+      local item_data = item_data_list[i]
+
+      if item_data[KEY_COLUMN] >= 0 then
+        item_data[KEY_COLNO_COMPUTED] = item_data[KEY_COLUMN]
+      else
+        if item_data[KEY_XPOS] <= left_column_x_limit then
+          item_data[KEY_COLNO_COMPUTED] = 1
+        else
+          item_data[KEY_COLNO_COMPUTED] = 2
+        end
+      end
+    end
+
+  end
+  for i=1,#item_data_list do
+    local item = item_data_list[i]
+
+    local pos = item[KEY_POS]
+    local colnocomputed = item[KEY_COLNO_COMPUTED]
+
+    if pos == POS_LEFT then
+      rightside = false
+    elseif pos == POS_RIGHT then
+      rightside = true
+    elseif pos == POS_NEAREST then
+      rightside = (item[KEY_XPOS] >= x_textmiddle[colnocomputed])
+    else
+      rightside = RIGHTSIDE_LOOKUP_TABLE[recto][colnocomputed][pos]
+    end
+
+    local marginno = MARGINNO_LOOKUP_TABLE[recto][colnocomputed][rightside]
+
+    if rightside then
+      item[KEY_SIDE_COMPUTED] = 0
+      item[KEY_XSHIFT_COMPUTED] = -item[KEY_XPOS]
+                                  + x_textright[colnocomputed]
+    else
+      item[KEY_SIDE_COMPUTED] = 1
+      item[KEY_XSHIFT_COMPUTED] = -item[KEY_XPOS]
+                                  + x_textleft[colnocomputed]
+    end
+    item[KEY_MARGINNO_COMPUTED] = marginno
+
+  end
+
+end
+local function get_y_item_top(item_data)
+  return item_data[KEY_YPOS]
+         + item_data[KEY_YSHIFT_COMPUTED]
+         + item_data[KEY_HEIGHT]
+end
+local function get_y_item_bottom(item_data)
+  return item_data[KEY_YPOS]
+         - item_data[KEY_DEPTH]
+         + item_data[KEY_YSHIFT_COMPUTED]
+end
+local function get_ysep_list(item_data_list)
+
+  local ysep_list = {}
+
+  ysep_list[0] = 0
+  for i=1,#item_data_list-1 do
+    ysep_list[i] = math.max(
+                     item_data_list[i][KEY_YSEP_BELOW],
+                     item_data_list[i+1][KEY_YSEP_ABOVE]
+                   )
+  end
+  ysep_list[#item_data_list] = 0
+
+  return ysep_list
+
+end
+local function compute_items_vertical_optfixed_enabled(item_data_list)
+
+  local optfixed_item_data_list = {}
+  local fixed_item_data_list = {}
+
+  for _,item_data in pairs(item_data_list) do
+    if item_data[KEY_TYPE] == TYPE_OPTFIXED then
+      optfixed_item_data_list[#optfixed_item_data_list+1] = item_data
+    elseif item_data[KEY_TYPE] == TYPE_FIXED then
+      fixed_item_data_list[#fixed_item_data_list+1] = item_data
+    end
+  end
+
+  for _,optfixed_item_data in pairs(optfixed_item_data_list) do
+    local optfixed_y_item_top = get_y_item_top(optfixed_item_data)
+    local optfixed_y_item_bottom = get_y_item_bottom(optfixed_item_data)
+
+    for _,fixed_item_data in pairs(fixed_item_data_list) do
+      local fixed_y_item_top = get_y_item_top(fixed_item_data)
+      local fixed_y_item_bottom = get_y_item_bottom(fixed_item_data)
+
+      if (
+        (
+          (fixed_y_item_bottom - optfixed_y_item_top)
+          <
+          math.max(
+            fixed_item_data[KEY_YSEP_BELOW],
+            optfixed_item_data[KEY_YSEP_ABOVE]
+          )
+        )
+        and
+        (
+          (optfixed_y_item_bottom - fixed_y_item_top)
+          <
+          math.max(
+            optfixed_item_data[KEY_YSEP_BELOW],
+            fixed_item_data[KEY_YSEP_ABOVE]
+          )
+        )
+      ) then
+        optfixed_item_data[KEY_ENABLED_COMPUTED] = false
+        break
+      end
+    end
+  end
+
+end
+local function compute_items_vertical_adjustment(item_data_list,page_data)
+  if #item_data_list == 0 then
+    return
+  end
+
+  local ysep_list = get_ysep_list(item_data_list)
+  local y_limit_above = (
+    page_data[KEY_VOFFSET]
+    + page_data[KEY_PAPERHEIGHT]
+    - item_data_list[1][KEY_YSEP_PAGE_TOP]
+  )
+
+  for i=1,#item_data_list do
+    local item_data = item_data_list[i]
+
+    local y_item_top = get_y_item_top(item_data)
+
+    if y_item_top > y_limit_above then
+      if item_data[KEY_TYPE] == TYPE_NORMAL then
+        item_data[KEY_YSHIFT_COMPUTED] = item_data[KEY_YSHIFT_COMPUTED]
+                                         + (y_limit_above - y_item_top)
+      end
+    end
+
+    y_limit_above = get_y_item_bottom(item_data) - ysep_list[i]
+  end
+  local y_limit_below = (
+    page_data[KEY_VOFFSET]
+    + item_data_list[#item_data_list][KEY_YSEP_PAGE_BOTTOM]
+  )
+
+  for i=#item_data_list,1,-1 do
+    local item_data = item_data_list[i]
+
+    local y_item_bottom = get_y_item_bottom(item_data)
+
+    if y_item_bottom < y_limit_below then
+      if item_data[KEY_TYPE] == TYPE_NORMAL then
+        item_data[KEY_YSHIFT_COMPUTED] = item_data[KEY_YSHIFT_COMPUTED]
+                                         + (y_limit_below - y_item_bottom)
+      end
+    end
+
+    y_limit_below = get_y_item_top(item_data) + ysep_list[i-1]
+  end
+
+end
+local ITEM_PASSED_YSEP_PAGE_TOP_MESSAGES = {
+  [TYPE_NORMAL] = 'Moveable item > ysep page top',
+  [TYPE_FIXED] = 'Topmost fixed item > ysep page top',
+  [TYPE_OPTFIXED] = 'Topmost optfixed item > ysep page top',
+}
+local ITEM_CLASH_MESSAGES = {
+  [TYPE_NORMAL] = {
+    [TYPE_NORMAL] = 'moveable items'
+                    .. ' (this shouldn\'t happen)',
+    [TYPE_FIXED] = 'moveable item above fixed item',
+    [TYPE_OPTFIXED] = 'moveable item above optfixed item',
+  },
+  [TYPE_FIXED] = {
+    [TYPE_NORMAL] = 'moveable item below fixed item',
+    [TYPE_FIXED] = 'fixed items',
+    [TYPE_OPTFIXED] = 'fixed item above optfixed item '
+                      .. '(this shouldn\'t happen)',
+  },
+  [TYPE_OPTFIXED] = {
+    [TYPE_NORMAL] = 'moveable items below optfixed item',
+    [TYPE_FIXED] = 'fixed item below optfixed item '
+                   .. '(this shouldn\'t happen)',
+    [TYPE_OPTFIXED] = 'optfixed items '
+                      .. '(this shouldn\'t happen)',
+  },
+}
+local ITEM_PASSED_YSEP_PAGE_BOTTOM_MESSAGE = {
+  [TYPE_NORMAL] = 'Moveable item < ysep page bottom',
+  [TYPE_FIXED] = 'Bottommost fixed item < ysep page bottom',
+  [TYPE_OPTFIXED] = 'Bottommost optfixed item < ysep page bottom',
+}
+local function check_items_vertical(item_data_list,page_data)
+  if (#item_data_list) == 0 then
+    return
+  end
+
+  local ysep_list = get_ysep_list(item_data_list)
+
+  local item_data
+
+  item_data = item_data_list[1]
+  if (
+       get_y_item_top(item_data) > page_data[KEY_VOFFSET]
+                                 + page_data[KEY_PAPERHEIGHT]
+                                 - item_data[KEY_YSEP_PAGE_TOP]
+  ) then
+    table.insert(
+      PROBLEM_REPORT_TABLE,
+      get_data_page_number(item_data)
+      .. ' ' .. ITEM_PASSED_YSEP_PAGE_TOP_MESSAGES[item_data[KEY_TYPE]]
+    )
+  end
+
+  for i=2,#item_data_list do
+    local item_data = item_data_list[i]
+    local prev_item_data = item_data_list[i-1]
+    if (
+      get_y_item_top(item_data) > get_y_item_bottom(prev_item_data)
+                                  - ysep_list[i-1]
+    ) then
+      table.insert(
+        PROBLEM_REPORT_TABLE,
+        get_data_page_number(item_data)
+        .. ' Clash: ' ..
+        ITEM_CLASH_MESSAGES[prev_item_data[KEY_TYPE]][item_data[KEY_TYPE]]
+      )
+    end
+  end
+  item_data = item_data_list[#item_data_list]
+  if (
+    get_y_item_bottom(item_data) < page_data[KEY_VOFFSET]
+                                   + item_data[KEY_YSEP_PAGE_BOTTOM]
+  ) then
+    table.insert(
+      PROBLEM_REPORT_TABLE,
+      get_data_page_number(item_data)
+      .. ' ' .. ITEM_PASSED_YSEP_PAGE_BOTTOM_MESSAGE[item_data[KEY_TYPE]]
+    )
+  end
+
+end
+local function compute_items_vertical(item_data_list,page_data)
+  for i=1,#item_data_list do
+    local item_data = item_data_list[i]
+
+    item_data[KEY_YSHIFT_COMPUTED] = item_data[KEY_YSHIFT]
+  end
+  compute_items_vertical_optfixed_enabled(item_data_list)
+  list_filter(item_data_list,function(item_data)
+    return item_data[KEY_ENABLED_COMPUTED]
+  end)
+  table.sort(
+    item_data_list,
+    function(left,right)
+      local y_diff = left[KEY_YPOS] - right[KEY_YPOS]
+
+      if y_diff > 0 then
+        return true
+      elseif y_diff < 0 then
+        return false
+      end
+
+      local x_diff = left[KEY_XPOS] - right[KEY_XPOS]
+
+      if x_diff < 0 then
+        return true
+      elseif x_diff > 0 then
+        return false
+      end
+
+      return (left[KEY_ITEMNO] < right[KEY_ITEMNO])
+    end
+  )
+
+  compute_items_vertical_adjustment(item_data_list,page_data)
+
+  check_items_vertical(item_data_list,page_data)
+
+end
+local function compute_items()
+  local max_abspageno = 0
+
+  for k,v in pairs(ITEM_DATA_MAIN_TABLE) do
+    max_abspageno = math.max(v[KEY_ABSPAGENO],max_abspageno)
+  end
+  local per_abspage_item_data_list = {}
+  for i=1,max_abspageno do
+    per_abspage_item_data_list[i] = {}
+  end
+  for _,item_data in pairs(ITEM_DATA_MAIN_TABLE) do
+    local temp_table = per_abspage_item_data_list[item_data[KEY_ABSPAGENO]]
+    temp_table[#temp_table+1] = item_data
+  end
+  local per_abspage_page_data_list = {}
+  local pagedatano = 0
+  for abspageno = 1,max_abspageno do
+    while (
+      PAGE_DATA_MAIN_TABLE[pagedatano+1] ~= nil
+      and
+      PAGE_DATA_MAIN_TABLE[pagedatano+1][KEY_ABSPAGENO] == abspageno
+    ) do
+      pagedatano = pagedatano+1
+    end
+    per_abspage_page_data_list[abspageno] = PAGE_DATA_MAIN_TABLE[pagedatano]
+  end
+  for abspageno=1,#per_abspage_item_data_list do
+    local current_page_data = per_abspage_page_data_list[abspageno]
+    local current_page_item_data_list = per_abspage_item_data_list[abspageno]
+    compute_items_horizontal(current_page_item_data_list,current_page_data)
+    local current_page_item_data_sublists = {}
+
+    for i=0,5 do
+      current_page_item_data_sublists[i] = {}
+    end
+
+    for _,item_data in pairs(current_page_item_data_list) do
+      table.insert(
+        current_page_item_data_sublists[item_data[KEY_MARGINNO_COMPUTED]],
+        item_data
+      )
+    end
+    for i=0,5 do
+      compute_items_vertical(
+        current_page_item_data_sublists[i],
+        current_page_data
+      )
+    end
+  end
+end
+local function load_item_data(itemno)
+
+  item = ITEM_DATA_MAIN_TABLE[tonumber(itemno)]
+  if item == nil then
+    item = ITEM_DATA_DEFAULTS
+  end
+
+  tex.count['l__marginalia_page_int'] = item[KEY_PAGENO]
+  tex.count['l__marginalia_column_computed_int'] = item[KEY_COLNO_COMPUTED]
+  tex.dimen['l__marginalia_xshift_computed_dim'] = item[KEY_XSHIFT_COMPUTED]
+  tex.dimen['l__marginalia_yshift_computed_dim'] = item[KEY_YSHIFT_COMPUTED]
+  tex.count['l__marginalia_side_computed_int'] = item[KEY_SIDE_COMPUTED]
+  tex.count['l__marginalia_marginno_computed_int']
+    = item[KEY_MARGINNO_COMPUTED]
+  if item[KEY_ENABLED_COMPUTED] then
+    tex.count['l__marginalia_enabled_computed_int'] = 1
+  else
+    tex.count['l__marginalia_enabled_computed_int'] = 0
+  end
+
+end
+return {
+  store_default_page_data = store_default_page_data,
+  store_page_data = store_page_data,
+  check_page_data = check_page_data,
+
+  store_item_data = store_item_data,
+  check_item_data = check_item_data,
+
+  compute_items = compute_items,
+
+  load_item_data = load_item_data,
+
+  write_problem_report = write_problem_report,
+
+  write_page_change_report = write_page_change_report,
+  write_item_change_report = write_item_change_report,
+}

Modified: trunk/Master/texmf-dist/tex/lualatex/marginalia/marginalia.sty
===================================================================
--- trunk/Master/texmf-dist/tex/lualatex/marginalia/marginalia.sty	2025-02-18 21:06:58 UTC (rev 74102)
+++ trunk/Master/texmf-dist/tex/lualatex/marginalia/marginalia.sty	2025-02-18 21:07:08 UTC (rev 74103)
@@ -21,7 +21,7 @@
 %% LaTeX version 2008-05-04 or later.
 %% 
 \NeedsTeXFormat{LaTeX2e}[2020-02-02]
-\ProvidesExplPackage{marginalia}{2025-02-17}{0.80.1}
+\ProvidesExplPackage{marginalia}{2025-02-18}{0.80.2}
   {Non-floating marginal content for LuaLaTeX}
 \sys_if_engine_luatex:F
   {
@@ -512,4 +512,3 @@
 {
   \__marginalia_write_page_data:
 }
-LUACODE



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