summaryrefslogtreecommitdiff
path: root/macros/luatex
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2025-02-19 03:04:01 +0000
committerNorbert Preining <norbert@preining.info>2025-02-19 03:04:01 +0000
commitfacfc58cb00299a25dbc22cea25d4ea12c1cd902 (patch)
tree9ddeb30522441a50d5acc02e8f6c045680429ea4 /macros/luatex
parentdb3bad71b584503efcbe1a9c0d4309826f93722f (diff)
CTAN sync 202502190303
Diffstat (limited to 'macros/luatex')
-rw-r--r--macros/luatex/generic/luamplib/NEWS9
-rw-r--r--macros/luatex/generic/luamplib/luamplib.dtx67
-rw-r--r--macros/luatex/generic/luamplib/luamplib.pdfbin300502 -> 301954 bytes
-rw-r--r--macros/luatex/generic/luamplib/test-luamplib-latex.tex12
-rw-r--r--macros/luatex/latex/marginalia/marginalia-doc-ysep-explanation.pdfbin436691 -> 436690 bytes
-rw-r--r--macros/luatex/latex/marginalia/marginalia-doc.pdfbin845420 -> 798476 bytes
-rw-r--r--macros/luatex/latex/marginalia/marginalia.dtx1340
7 files changed, 1411 insertions, 17 deletions
diff --git a/macros/luatex/generic/luamplib/NEWS b/macros/luatex/generic/luamplib/NEWS
index 9158d1ef92..eca51b064f 100644
--- a/macros/luatex/generic/luamplib/NEWS
+++ b/macros/luatex/generic/luamplib/NEWS
@@ -1,5 +1,14 @@
History of the luamplib package
+2025/02/18 2.37.1
+ * A new operator 'mplibuclength <string>' returns the number of unicode
+ grapheme clusters in the string.
+
+ * A new operator 'mplibucsubstring <pair> of <string>' returns the part of
+ the string indexed by unicode grapheme clusters.
+
+ * These two operators require lua-uni-algos package.
+
2025/02/06 2.37.0
* introduce a new operator 'mpliblength <string>', a unicode-aware version of
the 'length' primitive
diff --git a/macros/luatex/generic/luamplib/luamplib.dtx b/macros/luatex/generic/luamplib/luamplib.dtx
index c08d07f029..99414a7ef1 100644
--- a/macros/luatex/generic/luamplib/luamplib.dtx
+++ b/macros/luatex/generic/luamplib/luamplib.dtx
@@ -85,7 +85,7 @@ See source file '\inFileName' for licencing and contact information.
%<*driver>
\NeedsTeXFormat{LaTeX2e}
\ProvidesFile{luamplib.drv}%
- [2025/02/06 v2.37.0 Interface for using the mplib library]%
+ [2025/02/18 v2.37.1 Interface for using the mplib library]%
\documentclass{ltxdoc}
\usepackage{metalogo,multicol,xspace}
\usepackage[x11names]{xcolor}
@@ -155,7 +155,7 @@ See source file '\inFileName' for licencing and contact information.
% \author{Hans Hagen, Taco Hoekwater, Elie Roux, Philipp Gesang and Kim Dohyun\\
% Current Maintainer: Kim Dohyun\\
% Support: \url{https://github.com/lualatex/luamplib}}
-% \date{2025/02/06 v2.37.0}
+% \date{2025/02/18 v2.37.1}
%
% \maketitle
%
@@ -1084,7 +1084,7 @@ See source file '\inFileName' for licencing and contact information.
% \item[\texttt{withshadingcenter} \meta{pair}]
% Values for shifting starting center.
% For instance,
-% |(0,0)| means that center of starting circle is |center p|;
+% |(0,0)| means that the center of starting circle is |center p|;
% |(1,1)| means |urcorner p|.
% \item[\texttt{withshadingtransform} \meta{string}]
% where \meta{string} shall be |"yes"| (respect transform) or |"no"| (ignore transform).
@@ -1103,18 +1103,34 @@ See source file '\inFileName' for licencing and contact information.
% Default value: |(white,black)|
% \end{description}
%
-% \subsubsection{\texttt{mpliblength ...}}
+% \subsubsection{\texttt{mpliblength ...}, \texttt{mplibuclength ...}}
% |mpliblength| \meta{string} returns the number of unicode characters in the string.
% This is a unicode-aware version equivalent to the \metapost primitive |length|, but
% accepts only a string-type argument.
% For instance, |mpliblength| |"abçdéf"| returns |6|, not |8|.
%
-% \subsubsection{\texttt{mplibsubstring ... of ...}}
+% On the other hand,
+% |mplibuclength| \meta{string} returns the number of unicode grapheme clusters in the string.
+% For instance, |mplibuclength| |"Äpfel"|,
+% where |Ä| is encoded using two codepoints (U+0041 and U+0308),
+% returns |5|, not |6| or |7|.
+% This operator requires \pkg{lua-uni-algos} package.
+%
+% \subsubsection{\texttt{mplibsubstring ... of ...}, \texttt{mplibucsubstring ... of ...}}
% |mplibsubstring| \meta{pair} |of| \meta{string} is a unicode-aware version equivalent to the
-% \metapost's |substring ... of ...| primitive. The syntax is the same as the latter.
+% \metapost's |substring ... of ...| primitive. The syntax is the same as the latter,
+% but the string is indexed by unicode characters.
% For instance, |mplibsubstring| |(2,5)| |of| |"abçdéf"| returns |"çdé"|,
% and |mplibsubstring| |(5,2)| |of| |"abçdéf"| returns |"édç"|.
%
+% On the other hand,
+% |mplibucsubstring| \meta{pair} |of| \meta{string} returns the part of the string indexed
+% by unicode grapheme clusters.
+% For instance, |mplibucsubstring| |(0,1)| |of| |"Äpfel"|,
+% where |Ä| is encoded using two codepoints (U+0041 and U+0308),
+% returns |"Ä"|, not |"A"|.
+% This operator requires \pkg{lua-uni-algos} package.
+%
% \subsection{Lua}
%
% \subsubsection{\texttt{runscript ...}}
@@ -1200,8 +1216,8 @@ See source file '\inFileName' for licencing and contact information.
luatexbase.provides_module {
name = "luamplib",
- version = "2.37.0",
- date = "2025/02/06",
+ version = "2.37.1",
+ date = "2025/02/18",
description = "Lua package to typeset Metapost with LuaTeX's MPLib.",
}
@@ -2644,12 +2660,25 @@ end
% \end{macrocode}
%
-% lua function for |mplibsubstring ... of ...|
+% lua functions for |mplib(uc)substring ... of ...|
% \begin{macrocode}
-function luamplib.utf8substring (s,b,e)
- local t, tt, step = { }, { }
- for _, c in utf8.codes(s) do
- table.insert(t, utf8.char(c))
+function luamplib.getunicodegraphemes (s)
+ local t = { }
+ local graphemes = require'lua-uni-graphemes'
+ for _, _, c in graphemes.graphemes(s) do
+ table.insert(t, c)
+ end
+ return t
+end
+function luamplib.unicodesubstring (s,b,e,grph)
+ local tt, t, step = { }
+ if grph then
+ t = luamplib.getunicodegraphemes(s)
+ else
+ t = { }
+ for _, c in utf8.codes(s) do
+ table.insert(t, utf8.char(c))
+ end
end
if b <= e then
b, step = b+1, 1
@@ -3091,10 +3120,18 @@ def mpliblength primary t =
runscript("return utf8.len[===[" & t & "]===]")
enddef;
def mplibsubstring expr p of t =
- runscript("return luamplib.utf8substring([===[" & t & "]===],"
+ runscript("return luamplib.unicodesubstring([===[" & t & "]===],"
& decimal xpart p & ","
& decimal ypart p & ")")
enddef;
+def mplibuclength primary t =
+ runscript("return #luamplib.getunicodegraphemes[===[" & t & "]===]")
+enddef;
+def mplibucsubstring expr p of t =
+ runscript("return luamplib.unicodesubstring([===[" & t & "]===],"
+ & decimal xpart p & ","
+ & decimal ypart p & ",true)")
+enddef;
]],
legacyverbatimtex = [[
def specialVerbatimTeX (text t) = runscript("luamplibprefig{"&t&"}") enddef;
@@ -4564,7 +4601,7 @@ end
% \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{luamplib}
- [2025/02/06 v2.37.0 mplib package for LuaTeX]
+ [2025/02/18 v2.37.1 mplib package for LuaTeX]
\fi
\ifdefined\newluafunction\else
\input ltluatex
diff --git a/macros/luatex/generic/luamplib/luamplib.pdf b/macros/luatex/generic/luamplib/luamplib.pdf
index 95965a4f20..2033bb205d 100644
--- a/macros/luatex/generic/luamplib/luamplib.pdf
+++ b/macros/luatex/generic/luamplib/luamplib.pdf
Binary files differ
diff --git a/macros/luatex/generic/luamplib/test-luamplib-latex.tex b/macros/luatex/generic/luamplib/test-luamplib-latex.tex
index f983d96b17..6156a00f66 100644
--- a/macros/luatex/generic/luamplib/test-luamplib-latex.tex
+++ b/macros/luatex/generic/luamplib/test-luamplib-latex.tex
@@ -6,6 +6,7 @@
\fi
\usepackage{fontspec}
\setmainfont{latin modern roman}
+\newfontfamily\hangulfont{NotoSansCJKKR}[Script=Hangul,Language=Korean]
\usepackage{luamplib}
\usepackage{graphicx,xcolor}
\everymplib{ beginfig(0); }
@@ -353,12 +354,23 @@ draw fullcircle scaled 100 withpattern "mypatt" withpen pencircle scaled 1
withshadingcolors (red,blue)
;
\endmpfig
+\leavevmode
\mpfig
string Test; Test="abçdéf";
for k=0 upto mpliblength(Test)-1:
draw TEX(mplibsubstring (k,k+1) of Test) scaled 2 shifted (20k,0);
endfor
\endmpfig
+\qquad
+\mpliblegacybehavior{false}%
+\mpfig
+ verbatimtex \hangulfont etex;
+ string Test; Test="나랏말ᄊᆞ미";
+ for k=0 upto mplibuclength(Test)-1:
+ draw TEX(mplibucsubstring (k,k+1) of Test) scaled 1.5 shifted (20k,0);
+ endfor
+\endmpfig
+\mpliblegacybehavior{true}%
\tracingcommands0
\vskip 2\baselineskip
diff --git a/macros/luatex/latex/marginalia/marginalia-doc-ysep-explanation.pdf b/macros/luatex/latex/marginalia/marginalia-doc-ysep-explanation.pdf
index f667b1f5d6..0b87405f39 100644
--- a/macros/luatex/latex/marginalia/marginalia-doc-ysep-explanation.pdf
+++ b/macros/luatex/latex/marginalia/marginalia-doc-ysep-explanation.pdf
Binary files differ
diff --git a/macros/luatex/latex/marginalia/marginalia-doc.pdf b/macros/luatex/latex/marginalia/marginalia-doc.pdf
index de4e9c33a4..52b18c9c54 100644
--- a/macros/luatex/latex/marginalia/marginalia-doc.pdf
+++ b/macros/luatex/latex/marginalia/marginalia-doc.pdf
Binary files differ
diff --git a/macros/luatex/latex/marginalia/marginalia.dtx b/macros/luatex/latex/marginalia/marginalia.dtx
index 7d7bc9846f..867a3492e2 100644
--- a/macros/luatex/latex/marginalia/marginalia.dtx
+++ b/macros/luatex/latex/marginalia/marginalia.dtx
@@ -973,7 +973,7 @@ width=35mm]{This note is placed on the left side of the page, wider, in sans
% 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,7 +1923,1343 @@ width=35mm]{This note is placed on the left side of the page, wider, in sans
%
%
%
-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@pagedata} or
+% \cs{marginalia@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}
%
%
%