% \iffalse meta-comment
%
% Copyright (C) 2025 Alan J. Cain
%
% This file may be distributed and/or modified under the conditions of the LaTeX Project Public License, either version
% 1.3c of this license or (at your option) any later version. The latest version of this license is in:
%
% http://www.latex-project.org/lppl.txt
%
% and version 1.3c or later is part of all distributions of LaTeX version 2008-05-04 or later.
%
% \fi
%
% \iffalse
%<*driver>
\PassOptionsToPackage{inline}{enumitem}
\documentclass{l3doc}


\makeatletter
\ExplSyntaxOn

\cs_gset:Npn \l@subsection { \@dottedtocline{2}{2.5em}{2.8em} }  % #2 = 1.5em
\cs_gset:Npn \l@subsubsection { \@dottedtocline{3}{5.3em}{3.5em} }  % #2 = 1.5em
\cs_gset:Npn \l@paragraph { \@dottedtocline{4}{8.8em}{3.2em} }  % #2 = 1.5em

\ExplSyntaxOff
\makeatother


\usepackage{xcolor}

\definecolor{linkcolor}{rgb}{0.0,0.4,0.7}
\colorlet{citecolor}{linkcolor}
\colorlet{urlcolor}{linkcolor}

\hypersetup{
  linkcolor=linkcolor,%
  citecolor=citecolor,%
  urlcolor=urlcolor,%
}


\newcommand*\fullref[2]{%
  \hyperref[#2]{#1\penalty 200\ \ref*{#2}}%
}


\setcounter{tocdepth}{7}
\numberwithin{figure}{section}



\usepackage{marginalia}


\marginaliasetup{
  ysep=0pt,
  ysep page top=10mm,
  ysep page bottom=5mm,
}
\renewcommand*\marginpar[1]{\marginalia[pos=left]{#1}}


\usepackage{titleps}

\newpagestyle{sideheadings}[\normalfont]{
  \sethead{%
            \smash{%
              \marginalia[
                pos=left,
                valign=b,
                yshift={-\topskip-2\baselineskip},
                type=optfixed,
                xsep=2em,
                ysep={2\baselineskip},
                column=one,
                width=40mm,
              ]{%
                \raggedleft
                \itshape
                \large
                \sectiontitle
              }%
            }%
           }
          {}
          {}
  \setfoot{}
          {\thepage}
          {}
}





\usepackage{booktabs}
\usepackage{graphicx}


\newlist{vallist}{description}{1}
\setlist[vallist]{
  leftmargin=3em,
  style=unboxed,
  labelsep=1em,
  font=\descriptionitemcolon,
  nosep,
}

\newcommand*{\descriptionitemcolon}[1]{\kern 1em #1:}


\usepackage{siunitx}
\sisetup{
  mode=match,
}
\DeclareSIUnit\inch{in}
\DeclareSIUnit\point{pt}


\usepackage{tikz}
\usetikzlibrary{decorations.pathmorphing}


\newrobustcmd*\examplelabel[1]{%
  \smash{%
    \begin{tikzpicture}[baseline=(labelnode.base)]
      \node[node font=\sffamily\footnotesize] (labelnode) at (0,0) {#1};
      \draw[gray] (labelnode.center) circle (5pt);
    \end{tikzpicture}%
  }%
}


\usepackage{mathtools}

\DeclarePairedDelimiter{\abs}{\lvert}{\rvert}
\DeclarePairedDelimiter{\set}{\lbrace}{\rbrace}


\newcommand*\key[1]{\texttt{#1}}
\newcommand*\val[1]{\texttt{#1}}
\newcommand*\keyvalue[2]{\texttt{#1=#2}}

\NewDocumentCommand{\default}{ m }{(\textit{Default:} #1)}


\newcommand*\luafunc[1]{\texttt{#1}}
\newcommand*\luavar[1]{\texttt{#1}}


\usepackage{listings}

\lstset{
  language=[LaTeX]TeX,
  basicstyle=\small\ttfamily,
  basewidth=0.5em,
}



\newcounter{sidenote}
\newcommand*\sidenote[1]{%
  \stepcounter{sidenote}%
  \textsuperscript{\thesidenote}%
  \marginalia[
    pos=left,
    style={\raggedright\footnotesize},
    width=33mm,
    xsep=1em,
  ]{%
    \leavevmode\strut\llap{\thesidenote~}#1\strut%
  }%
}



\begin{document}

\DocInput{\jobname.dtx}

\PrintIndex

\end{document}
%</driver>
% \fi
%
%
%
% \GetFileInfo{marginalia.sty}
%
%
%
% \title{^^A
% \pkg{marginalia} ^^A
%   --- Non-floating marginal content with automatic placement for Lua\LaTeX^^A
%   \thanks{This file describes \fileversion, last revised \filedate.}^^A
% }
%
% \author{^^A
%  Alan J. Cain^^A
% }
%
% \date{Released \filedate}
%
% \maketitle
%
%
%
% \begin{abstract}
%   This Lua\LaTeX\ package allows the placement of marginal content anywhere, without \cs{marginpar}'s limits, and
%   automatically adjusts positions to prevent overlaps or content being pushed off the page. In short, it tries to
%   combine the best features from the packages \pkg{marginnote}, \pkg{marginfix} and \pkg{marginfit} with key--value
%   settings that allow fine-grained customization.
% \end{abstract}
%
%
%
% \tableofcontents
%
%
%
% \begin{documentation}
%
%
%
% \section{Introduction}
% \pagestyle{sideheadings}
%
% The \LaTeX\ \cs{marginpar} command is the basic method for placing content in the margin. For purposes such as drawing
% attention to particular points in the text, it functions well. Its main limitation is that \cs{marginpar} works via
% the \LaTeX\ float mechanism and so cannot be used to create marginal content next to a figure, table, or other float,
% or next to a footnote, or to place running heads in the margin, such as are found in the left-hand margin of this
% document except for the `implementation' section. (Bringhurst called this style `running shoulder\-heads'
% \cite[p.~65]{bringhurst_elements}, but the term may be non-standard.)
%
% Trying to set many separate pieces of marginal content using \cs{marginpar} can lead to other problems. If two
% \texttt{marginpar}s would clash, \LaTeX\ shifts the second item downward. But the cumulative effect can lead to
% \texttt{marginpar}s being shifted downward off the bottom of the page. Further, the asynchronous nature of \TeX's
% page-breaking can cause:
% \begin{enumerate*}[label={(\arabic*)}]
%   \item a \texttt{marginpar} to be placed in the wrong margin;
%   \item the topmost \texttt{marginpar} on a page to be unnecessarily shifted downward because of a hypothetical clash
%   that would have occured with the previous \texttt{marginpar}, had they been on the same page.
% \end{enumerate*}
%
% Packages like \pkg{mparhack}\sidenote{\textsc{url:} \url{https://ctan.org/pkg/mparhack}} (Tom Sgouros \& Stefan
% Ulrich), \pkg{marginnote}\sidenote{\textsc{url}: \url{https://ctan.org/pkg/marginnote}} (Markus Kohm),
% \pkg{marginfix}\sidenote{\textsc{url:} \url{https://ctan.org/pkg/marginfix}} (Stephen Hicks) and
% \pkg{marginfit}\sidenote{\textsc{url:} \url{https://ctan.org/pkg/marginfit}} (Maurice Leclaire) were created to avoid
% these limitations and problems. \pkg{mparhack} only ensures that each \texttt{marginpar} appears on the correct side
% of the page. \pkg{marginnote} allows marginal content to be placed anywhere, but does not adjust positions to avoid
% clashes. \pkg{marginfix} adjusts positions, but the unadjusted vertical positioning can be slightly off, and the
% package still uses floats. \pkg{marginfit} gets positions exactly right, but uses the insert mechanism and so marginal
% content cannot appear next to floats or footnotes.
%
% This Lua\LaTeX\ package, \pkg{marginalia}, provides a \cs{marginalia} command that attempts to avoid these
% limitations. Marginal content is placed, not via floats or inserts, but by a calculated per-item horizontal shift
% inside an (invisible) \cs{rlap} or \cs{llap} from the position where the \cs{marginalia} command was issued (which is
% similar to the technique used by \pkg{marginnote}), plus a calculated per-item vertical shift to avoid clashes with
% other content. The vertical shift is usually downward, but may be upward when necessary to prevent content from being
% shifted off the bottom of the page (which is similar to the vertical shifts performed by \pkg{marginfix} and
% \pkg{marginfit}).
%
% The calculation of the horizontal and vertical shifts uses information written to the \file{.aux} file during the
% previous Lua\LaTeX\ run. It thus takes at least two runs for all content to appear in the correct places. The package
% reports any changes from the previous run and any problems encountered.
%
% \bigskip
%
% \noindent\emph{Caveat:} \pkg{marginalia} was written to typeset running heads in the margin, sidenote references,
% side-captions for floats, and small marginal figures in the author's book \textit{Form \& Number: A History of
% Mathematical Beauty} \cite{cain_formandnumber_ebook_large}.\sidenote{\textit{Form \& Number} is freely available on
% the Internet Archive under a Creative Commons licence.
% \textsc{url}:~\url{https://archive.org/details/cain_formandnumber_ebook_large}} Thus the basic functionality has been
% tested extensively, and it has performed correctly.
%
%
%
% \paragraph*{Licence.} \noindent\pkg{marginalia} is released under the \LaTeX\ Project Public Licence v1.3c or
% later.\footnote{\textsc{url}: \url{https://www.latex-project.org/lppl.txt}}
%
%
%
% \section{Requirements}
%
% \pkg{marginalia} requires
% \begin{enumerate*}[label={(\arabic*)}]
%   \item Lua\LaTeX,
%   \item a recent \LaTeX\ kernel with \pkg{expl3} support (any kernel version since 2020-02-02 should suffice).
% \end{enumerate*}
% It does not depend on any other packages.
%
%
%
% \section{Installation}
%
% To install \pkg{marginalia} manually, run \texttt{luatex marginalia.ins} and copy \texttt{marginalia.sty} and
% \texttt{marginalia.lua} to somewhere Lua\LaTeX\ can find them.
%
%
%
% \section{Getting started}
%
% \pkg{marginalia} works `out of the box'. Load the package (there are no package options) and use the main
% \cs{marginalia} command to place marginal content. \fullref{Figure}{fig:getting-started} shows the source code for a
% small demonstration and the resulting document. \emph{The source code must be processed \textsl{twice} by Lua\LaTeX\
% for the marginal content to be placed correctly.} (See \fullref{Section}{sec:usage} for discussion of the need for
% multiple runs.)
%
% \begin{figure}[t]
%   \lstinputlisting{marginalia-doc-example.tex}
%   \begin{tikzpicture}
%     \pgfmathsetmacro{\s}{(\textwidth+.4pt)/210mm}
%     \pgfmathsetlengthmacro{\w}{\s*210mm}
%     \pgfmathsetlengthmacro{\h}{\s*100mm}
%     \path[save path=\outline] (0,0) -| ++(\w,-\h) -| cycle;
%     \begin{scope}
%       \clip[use path=\outline];
%       \node[anchor=north west,inner sep=0] at (0,0) {\includegraphics[scale=\s]{marginalia-doc-example.pdf}};
%     \end{scope}
%     \draw[gray,use path=\outline];
%   \end{tikzpicture}
%
%   \caption{A small demonstration of \pkg{marginalia}.}
%   \label{fig:getting-started}
% \end{figure}
%
% Turn to \fullref{Section}{sec:commands} for a detailed description of the available user commands, and
% \fullref{Section}{sec:options} for the various options (such as \keyvalue{style}{\meta{code}}) than can be used to
% change the placement and formatting of the marginal content.
%
%
%
% \section{User commands}
% \label{sec:commands}
%
% \begin{function}{\marginalia}
%   \begin{syntax}
%     \cs{marginalia}\oarg{options}\marg{content}
%   \end{syntax}
%   This is the basic command for placing marginal content. The \meta{content} can, roughly speaking, be anything: text,
%   mathematics, included graphics, Ti\textit{k}Z. The optional argument \meta{options} is a key--value list that
%   specifies how the content is typeset. The keys are described in \fullref{Subsection}{sec:options}.
% \end{function}
%
%
%
% \begin{function}{\marginaliasetup}
%   \begin{syntax}
%     \cs{marginaliasetup}\marg{options}
%   \end{syntax}
%   This command is used to set options globally. The argument \meta{options} is the same kind of key--value list as the
%   \meta{options} argument for the \cs{marginalia} command, and are described in \fullref{Subsection}{sec:options}.
% \end{function}
%
%
%
% \begin{function}{\marginalianewgeometry}
%   \begin{syntax}
%     \cs{marginalianewgeometry}
%   \end{syntax}
%   This command signals to \pkg{marginalia} that the page layout has been changed, for instance by using the
%   \cs{newgeometry} command from the \pkg{geometry} package,\sidenote{\textsc{url}:
%   \url{https://ctan.org/pkg/geometry}} or by using the \LaTeX\ command \cs{twocolumn} to switch to two-column mode. It
%   should be issued immediately after such a change, and certainly before the first page with the new layout has been
%   shipped out. There is no harm in using it unnecessarily.
% \end{function}
%
%
%
% \subsection{Access to page and column}
%
% Two counters available within the \meta{content} of \cs{marginalia} specify the actual page and column in which the
% call to \cs{marginalia} appears. These counters can be used to select different actions depending on the page on which
% the content appears or (in two-column mode) whether it pertains to the left or right column. It is best to use the
% variants of the \key{style} and \key{width} keys if marginal content should have different widths or styles depending
% on whether they appear on a recto/verso page or pertain to a particular column. These counters are made available for
% purposes not covered by the \key{style} and \key{width} variants.
%
% \begin{variable}{\marginaliapage}
%   A counter register, available within the \meta{content} of \cs{marginalia}, that holds the actual page on which the
%   marginal content appears. The value is based on the previous Lua\LaTeX\ run and will default to \(1\).
% \end{variable}
%
% \begin{variable}{\marginaliacolumn}
%   A counter register, available within the \meta{content} of \cs{marginalia}, that holds the actual column to which
%   the marginal content pertains. The value is \(1\) for the left column, \(2\) for the right column. In one-column
%   mode, the value is always \(0\). (If the key \key{column} is used to manually specify the column to which the
%   content pertains, the value of \cs{marginaliacolumn} will change accordingly.) The value is based on the previous
%   Lua\LaTeX\ run and will default to \(0\).
% \end{variable}
%
%
%
% \section{Options}
% \label{sec:options}
%
% The description of keys in this section, which are summarized in \fullref{Table}{tbl:keys-summary}, should be read in
% conjunction with the discussion of how marginal content is placed in \fullref{Section}{sec:placement}. In particular,
% the variants of the keys \key{width} and \key{style} follow the terminology shown in
% \fullref{Figure}{fig:terminology}.
%
% \begin{table}[p]
%   \centering
%   \caption{Summary of keys that can be set using \cs{marginaliasetup} or passed in the optional argument to
%   \cs{marginalia}.}
%   \label{tbl:keys-summary}
%   \begin{tabular}{lll}
%     \toprule
%     Key name                                                        & Value                                                 & Default                   \\
%     \midrule
%     \key{type}                                                      & \(\set{\val{normal},\val{fixed},\val{optfixed}}\)     & \val{normal}              \\
%     \midrule
%     \key{pos}                                                       & \parbox[t]{40mm}{\raggedright\hangindent=2em\(\set{\val{auto},\val{reverse},\val{left},\allowbreak
%                                 \val{right},\val{nearest}}\)\strut} & \val{auto}                                                                        \\
%     \key{column}                                                    & \(\set{\val{auto},\val{one},\val{left},\val{right}}\) & \val{auto}                \\
%     \key{xsep}                                                      & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{xsep outer}                                                & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{xsep inner}                                                & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{xsep between}                                              & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{xsep recto outer}                                          & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{xsep recto inner}                                          & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{xsep verso outer}                                          & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{xsep verso inner}                                          & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{xsep right between}                                        & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{xsep left between}                                         & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \midrule
%     \key{valign}                                                    & \(\set{\val{t},\val{b}}\)                             & \val{t}                   \\
%     \key{yshift}                                                    & Dimension                                             & \qty{0}{\point}           \\
%     \key{ysep}                                                      & Dimension                                             & \val{\cs{marginparpush}}  \\
%     \key{ysep above}                                                & Dimension                                             & \val{\cs{marginparpush}}  \\
%     \key{ysep below}                                                & Dimension                                             & \val{\cs{marginparpush}}  \\
%     \key{ysep page top}                                             & Dimension                                             & \val{\cs{marginparpush}}  \\
%     \key{ysep page top}                                             & Dimension                                             & \val{\cs{marginparpush}}  \\
%     \midrule
%     \key{width}                                                     & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{width outer}                                               & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{width inner}                                               & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{width between}                                             & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{width recto outer}                                         & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{width recto inner}                                         & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{width verso outer}                                         & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{width verso inner}                                         & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{width right between}                                       & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{width left between}                                        & Dimension                                             & \val{\cs{marginparwidth}} \\
%     \key{style}                                                     & \LaTeX\ code                                          & [Empty]                   \\
%     \key{style recto outer}                                         & \LaTeX\ code                                          & [Empty]                   \\
%     \key{style recto inner}                                         & \LaTeX\ code                                          & [Empty]                   \\
%     \key{style verso outer}                                         & \LaTeX\ code                                          & [Empty]                   \\
%     \key{style verso inner}                                         & \LaTeX\ code                                          & [Empty]                   \\
%     \key{style right between}                                       & \LaTeX\ code                                          & [Empty]                   \\
%     \key{style left between}                                        & \LaTeX\ code                                          & [Empty]                   \\
%     \bottomrule
%   \end{tabular}
% \end{table}
%
%
%
% \subsection{Type}
%
% \DescribeOption{type} The \key{type} of an item of marginal content can be set to one of the following three values:
% \begin{vallist}
%   \item[\val{normal}] The vertical position of the item will be changed automatically if necessary to prevent a clash
%   with another item of content.
%   \item[\val{fixed}] The vertical position of the item will \emph{never} be changed automatically from the position
%   specified by \key{yshift}, even if there is a clash with another item. (The type \val{fixed} was designed for
%   setting float captions in the margin, since a caption should not move away from the float with which it is
%   associated.)
%   \item[\val{optfixed}] The vertical position of the item will \emph{never} be changed automatically from the position
%   specified by \key{yshift}, even if there is a clash with another item. But an \val{optfixed} item will not appear in
%   the document if it would clash with a \val{fixed} item. (The type \val{optfixed} was designed for setting running
%   heads in the margin, which should not appear if they would clash with a figure caption set in the margin.)
% \end{vallist}
% \default{\val{normal}}
%
%
%
% \subsection{Horizontal placement}
%
% \DescribeOption{pos} The position in which an item of marginal content should be placed. It can be set to one of the
% the following four values:
% \begin{vallist}
%   \item[\val{auto}] Place the item in the default position as described in \fullref{Section}{sec:placement}: the outer
%   margin in single-column mode, and on the opposite side from the other column in two-column mode.
%   \item[\val{reverse}] Place the item on the opposite side of the text block (in one-column mode) or column (in
%   two-column mode) from \val{auto}.
%   \item[\val{left}] The left side of the text block or column.
%   \item[\val{right}] The right side of the text block of column.
%   \item[\val{nearest}] The side of the text block or column nearest to which \cs{marginalia} was called.
% \end{vallist}%
% \default{\val{auto}}
%
%
% \medskip\goodbreak
%
%
% \DescribeOption{column} In two-column mode, \pkg{marginalia} tries to determine to which column an item of marginal
% content pertains using the position of the call to \cs{marginalia}. If the call is to the left of the mid-point
% between the columns, the item is assumed to pertain to the left column; otherwise, it is assumed to pertain to the
% right column. In certain situations, this might lead to undesired placement of the item. In particular, any call to
% \cs{marginalia} in a full-width float in two-column mode would be handled as if it were a call from one of the columns
% and might thus be set in the wrong place. Similarly, an overfull hbox or a piece of \cs{rlap}-ped text might carry a
% call to \cs{marginalia} from the left column text into the area of the page occupied by the right column.
%
% The key \key{column} can be used to specify which column \pkg{marginalia} should place the item in. It can be set to
% one of four values:
% \begin{vallist}
%   \item[\val{auto}] Automatically determine which column an item of marginal content is placed in.
%   \item[\val{one}] Treat the item as being called from one-column mode.
%   \item[\val{left}] Treat the item as pertaining to the left column.
%   \item[\val{right}] Treat the item as pertaining to the right column.
% \end{vallist}
% The value of \key{column} has no effect in one-column mode. \default{\val{auto}}
%
%
% \medskip\goodbreak
%
%
% \DescribeOption{xsep}
% \DescribeOption{xsep outer}
% \DescribeOption{xsep inner}
% \DescribeOption{xsep between}
% \DescribeOption{xsep recto outer}
% \DescribeOption{xsep recto inner}
% \DescribeOption{xsep verso outer}
% \DescribeOption{xsep verso inner}
% \DescribeOption{xsep right between}
% \DescribeOption{xsep left between}
% These keys specify the horizontal separation between an item of marginal content and the text block next
% to which it is placed. Which separation is used will depend on where the item is typeset. The terminology is as
% in \fullref{Figure}{fig:terminology}.
% \begin{vallist}
%   \item[\key{xsep recto outer}] used for an item in the outer margin of a recto page.
%   \item[\key{xsep recto inner}] used for an item in the inner margin of a recto page.
%   \item[\key{xsep verso outer}] used for an item in the outer margin of a verso page.
%   \item[\key{xsep verso inner}] used for an item in the inner margin of a verso page.
%   \item[\key{xsep right between}] used for an item set from the right column between the columns.
%   \item[\key{xsep left between}] used for an item set from the left column between the columns.
%   \item[\key{xsep outer}] a shorthand for setting the keys \key{xsep recto outer} and \key{xsep verso outer}
%   simultaneously to the same value.
%   \item[\key{xsep inner}] a shorthand for setting the keys \key{xsep recto inner} and \key{xsep verso inner}
%   simultaneously to the same value.
%   \item[\key{xsep between}] a shorthand for setting the keys \key{xsep right between} and \key{xsep left between}
%   simultaneously to the same value.
% \item[\key{xsep}] a shorthand for setting all of these keys simultaneously.
% \end{vallist}
% (The shorthands \key{xsep outer} and \key{xsep inner} exist because page geometry is usually symmetrical between recto
% and verso pages as regards outer and inner margins. The shorthand \key{xsep between} exists because the space between
% columns, if used at all for marginal content, will often be shared equally.) Each of these keys must be set to a valid
% dimension. \default{value of \cs{marginparsep} when the package is loaded}
%
%
%
% \subsection{Vertical placement}
%
% \DescribeOption{valign} The option \key{valign} can be either \val{t} or \val{b}. In the former case, the baseline of
% the marginal content item is the baseline of the topmost box in its contents; in the latter case, its baseline is the
% baseline of the bottommost box in its contents. (Essentially, \cs{vtop} and \cs{vbox} are used to set the two options)
% \default{\val{t}}
%
%
% \medskip\goodbreak
%
%
% \DescribeOption{yshift}
% The key \key{yshift} is used to shift the default position of the marginal content item up (positive) or
% down (negative) from its normal position, which is to have its baseline aligned with the baseline of the callout
% position. It must be set to a valid dimension. Note that if \keyvalue{type}{normal}, then the vertical
% position may be adjusted from that specified by \key{yshift}. If this is not desired, specify a different \key{type}.
% \default{0pt}.
%
%
% \medskip\goodbreak
%
%
% \DescribeOption{ysep}
% \DescribeOption{ysep above}
% \DescribeOption{ysep below}
% \DescribeOption{ysep page top}
% \DescribeOption{ysep page bottom}
% These keys specify the minimum vertical separation above and below an item of marginal content
% \begin{vallist}
%   \item[\key{ysep above}] the minimum vertical separation between an item and the one above.
%   \item[\key{ysep below}] the minimum vertical separation between an item and the one below.
%   \item[\key{ysep page top}] the minimum vertical separation between an item and top of the page.
%   \item[\key{ysep page bottom}] the minimum vertical separation between an item and bottom of the page.
%   \item[\key{ysep}] is a shorthand for setting all of these keys simultaneously to the same value.
% \end{vallist}
% (See \fullref{Figure}{fig:ysep-explanation}.) Each of these keys must be set to a valid dimension. \default{value of
% \cs{marginparpush} when the package is loaded}.
%
% \begin{figure}[t]
%   \centering
%   \includegraphics{marginalia-doc-ysep-explanation.pdf}
%   \caption{(Illustration of \key{ysep}) The length \examplelabel{1} is at least the value of \key{ysep below}
%   specified (locally or globally) for marginal content item \examplelabel{A} and at least the value of \key{ysep
%   above} specified for item \examplelabel{B}. In this example diagram, \examplelabel{B} has been automatically moved
%   down from its natural position to maintain the required distance. Similarly, the length \examplelabel{2} is at least
%   the value of \key{ysep below} specified for \examplelabel{C} and at least the value of \key{ysep above} specified
%   for \examplelabel{D}, and the length \examplelabel{3} is at least the value of \key{ysep page bottom} specified for
%   \examplelabel{D}. In this example, to maintain the required distances, \examplelabel{C} and \examplelabel{D} have
%   been automatically moved (respectively) up and down from their natural positions.}
%   \label{fig:ysep-explanation}
% \end{figure}
%
%
%
% \subsection{Appearance}
%
% An item of marginal content that appears in the inner margin might be narrower than one that appears in the outer
% margin, and an item appearing in the outer margin of a recto page might be set ragged right, while an item appearing
% in the outer margin of a verso page might be set ragged left. And since it is not known where an item will appear
% until the page is assembled, the keys in this subsection, dealing with the width and style of an item, have variants
% that apply depending on where the item appears on the page.
%
%
% \medskip\goodbreak
%
%
% \DescribeOption{width}
% \DescribeOption{width outer}
% \DescribeOption{width inner}
% \DescribeOption{width between}
% \DescribeOption{width recto outer}
% \DescribeOption{width recto inner}
% \DescribeOption{width verso outer}
% \DescribeOption{width verso inner}
% \DescribeOption{width right between}
% \DescribeOption{width left between}
% These keys specify the width of the an item of marginal content (or, more precisely, the \cs{hsize} of the box into
% which the item is typeset). Which width is chosen will depend on the where the item is typeset. The terminology is as
% in \fullref{Figure}{fig:terminology}.
% \begin{vallist}
%   \item[\key{width recto outer}] used for an item in the outer margin of a recto page.
%   \item[\key{width recto inner}] used for an item in the inner margin of a recto page.
%   \item[\key{width verso outer}] used for an item in the outer margin of a verso page.
%   \item[\key{width verso inner}] used for an item in the inner margin of a verso page.
%   \item[\key{width right between}] used for an item set from the right column and placed between the columns.
%   \item[\key{width left between}] used for an item set from the right column and placed between the columns.
%   \item[\key{width outer}] a shorthand for setting the keys \key{width recto outer} and \key{width verso outer}
%   simultaneously to the same value.
%   \item[\key{width inner}] a shorthand for setting the keys \key{width recto inner} and \key{width verso inner}
%   simultaneously to the same value.
%   \item[\key{width between}] a shorthand for setting the keys \key{width right between} and \key{width left between}
%   simultaneously to the same value.
% \item[\key{width}] a shorthand for setting all of these keys simultaneously.
% \end{vallist}
% (The shorthands \key{width outer} and \key{width inner} exist because page geometry is usually symmetrical between
% recto and verso pages as regards outer and inner margins. The shorthand \key{width between} exists because the space
% between columns, if used at all for marginal content, will often be shared equally.) Each of these keys must be set to
% a valid dimension. \default{value of \cs{marginparwidth} when the package is loaded}
%
%
% \medskip\goodbreak
%
%
% \DescribeOption{style}
% \DescribeOption{style recto outer}
% \DescribeOption{style recto inner}
% \DescribeOption{style verso outer}
% \DescribeOption{style verso inner}
% \DescribeOption{style right between}
% \DescribeOption{style left between}
% These keys specify the style with which an item of marginal content is typeset. Which style is chosen will depend on
% where the item is typeset. The terminology is as in \fullref{Figure}{fig:terminology}.
% \begin{vallist}
%   \item[\key{style recto outer}] used for an item in the outer margin of a recto page.
%   \item[\key{style recto inner}] used for an item in the inner margin of a recto page.
%   \item[\key{style verso outer}] used for an item in the outer margin of a verso page.
%   \item[\key{style verso inner}] used for an item in the inner margin of a verso page.
%   \item[\key{style right between}] used for an item set from the right column between the columns.
%   \item[\key{style left between}] used for an item set from the right column between the columns.
%   \item[\key{style}] a shorthand for setting all of these keys simultaneously.
% \end{vallist}
% Each of these keys should be set to \LaTeX\ code that specifies the style. \default{[Empty]}
%
%
%
% \section{Placement}
% \label{sec:placement}
%
% The placement of an item of marginal content depends on where the call to \cs{marginalia} appears in the finished
% document. Both horizontal and vertical placement can be complicated.
%
%
%
% \subsection{Horizontal placement}
%
% To understand the horizontal placement, first recall some terminology: a recto page is an odd-numbered page in
% two-sided mode, or any page in one-sided mode; a verso page is an even-numbered page in two-sided mode. The
% description in the paragraphs that follow is summarized in \fullref{Figure}{fig:terminology}.
%
% \begin{figure}[t]
%   \centering
%   \begin{tikzpicture}[
%     x={.45*\textwidth},
%     y={sqrt(2)*.45*\textwidth},
%     ]
%
%     \begin{scope}[
%       every node/.style ={
%         node font=\footnotesize\scshape,
%         align=center,
%       }
%       ]
%       \begin{scope}
%         \clip[decorate,decoration={snake,amplitude=1mm,segment length=5mm}] (-1,0) -- (1,0) -- (1,.5) -| cycle;
%         \begin{scope}[fill=lightgray]
%           \fill (-.7,-.35) rectangle (-.2,.35);
%           \node at (-.45,.175) {one\\column};
%           \fill (.7,-.35) rectangle (.2,.35);
%           \node at (.45,.175) {one\\column};
%         \end{scope}
%       \end{scope}
%
%       \begin{scope}
%         \clip[decorate,decoration={snake,amplitude=1mm,segment length=5mm}] (-1,0) -- (1,0) -- (1,-.5) -| cycle;
%         \begin{scope}[fill=lightgray]
%           \fill (-.85,-.35) rectangle (-.65,.35);
%           \node at (-.75,-.175) {left\\column};
%           \fill (-.35,-.35) rectangle (-.15,.35);
%           \node at (-.25,-.175) {right\\column};
%           \fill (.35,-.35) rectangle (.15,.35);
%           \node at (.25,-.175) {left\\column};
%           \fill (.85,-.35) rectangle (.65,.35);
%           \node at (.75,-.175) {right\\column};
%         \end{scope}
%       \end{scope}
%
%     \end{scope}
%
%     \pgfresetboundingbox
%
%     \draw[white,line width=1pt,decorate,decoration={snake,amplitude=1mm,segment length=5mm}] (-1,0) -- (1,0);
%
%     \draw (0,-.5) -- (0,.5);
%     \draw (-1,-.5) rectangle (1,.5);
%
%     \begin{scope}[
%       every node/.style={
%         node font=\footnotesize\ttfamily,
%         inner xsep=3pt,
%       }
%         ]
%       \node[anchor=north west,align=left] at (.7,.35) {auto\\right};
%       \node[anchor=north east,align=right] at (.2,.35) {reverse\\left};
%
%       \node[anchor=south east,align=right] at (.15,-.35) {auto\\left};
%       \node[anchor=north,shift={(.03,0)},align=left] at (.35,-.35) {reverse\\right};
%       \node[anchor=north,shift={(-.03,0)},align=right] at (.65,-.35) {reverse\\left};
%       \node[anchor=south west,align=left] at (.85,-.35) {auto\\\strut\smash{right}};
%
%       \node[anchor=north east,align=right] at (-.7,.35) {auto\\left};
%       \node[anchor=north west,align=left] at (-.2,.35) {reverse\\right};
%
%       \node[anchor=south east,align=right] at (-.85,-.35) {auto\\left};
%       \node[anchor=north,shift={(.03,0)},align=left] at (-.65,-.35) {reverse\\right};
%       \node[anchor=north,shift={(-.03,0)},align=right] at (-.35,-.35) {reverse\\left};
%       \node[anchor=south west,align=left] at (-.15,-.35) {auto\\right};
%
%     \end{scope}
%
%     \begin{scope}[
%       every node/.style={
%         node font=\small\ttfamily,
%         inner xsep=6pt,
%       }
%       ]
%       \node[rotate=90,anchor=north] at (-1,0) {verso outer};
%       \node[rotate=90,anchor=north east,align=right] at (-.65,0) {left\\[-1pt]between};
%       \node[rotate=-90,anchor=north west,align=left] at (-.35,0) {right\\[-1pt]between};
%       \node[rotate=-90,anchor=north] at (0,0) {verso inner};
%       \node[rotate=90,anchor=north] at (0,0) {recto inner};
%       \node[rotate=90,anchor=north east,align=right] at (.35,0) {left\\[-1pt]between};
%       \node[rotate=-90,anchor=north west,align=left] at (.65,0) {right\\[-1pt]between};
%       \node[rotate=-90,anchor=north] at (1,0) {recto outer};
%     \end{scope}
%
%   \end{tikzpicture}
%   \caption{Summary of the positioning of marginal content using \key{pos}, and terminology used in \key{width} and
%   \key{style} keys, on recto and verso pages, in both one-column and two-column mode.}
%   \label{fig:terminology}
% \end{figure}
%
%
% In one-column mode, marginal content is placed by default in the outer margin: right on recto pages, left on verso
% pages. If \keyvalue{pos}{reverse} is applied, it is placed in the inner margin: left on recto pages, right on verso
% pages.
%
% In two-column mode, the default placement is next to the column in which the call to \cs{marginalia} appears, on the
% side opposite to the other column. Thus, if the call to \cs{marginalia} was in the left column, the marginal content
% item is placed by default on the left: on a recto page, the inner margin, on a verso page, the outer margin. If
% \keyvalue{pos}{reverse} is applied, it is placed between the two columns, adjacent to the left column. If the call to
% \cs{marginalia} was in the right column, the item is placed by default on the right: on a recto page, the
% outer margin, on a verso page, the inner margin. If \keyvalue{pos}{reverse} is applied, it is placed between the two
% columns, adjacent to the right column.
%
% \keyvalue{pos}{left} specifies that the item is to be placed on the left of the text block or column
% containing the call to \cs{marginalia}.
%
% \keyvalue{pos}{right} similarly specifies that the item is to be placed on the right of the text block or column
% containing the call to \cs{marginalia}.
%
% \pkg{marginalia} determines in which column the call to \cs{marginalia} was made using its horizontal position. As
% discussed in the description of key \key{column}, there are situations where this can go wrong and which
% necessitate a manual specification of a particular column.
%
%
%
% \subsection{Vertical placement}
% \label{subsec:vertical-placement}
%
% \pkg{marginalia} tries by default to place the each item of marginal content with its baseline shifted by the value of
% \key{yshift} (by default, \qty{0}{\point}) from the baseline where \cs{marginalia} was called. The actual vertical
% placement is calculated by the procedure described below, carried out for the items appearing in a particular
% horizontal location. (As shown in \fullref{Figure}{fig:terminology}, in one-column mode the possible locations are in
% outer and inner margins; in two-column mode the possible locationd are the outer and inner margins and on the left and
% right sides of the space between the columns.) A \emph{clash} exists when two items are closer than specified by
% \key{ysep below} for the upper item or \key{ysep above} for the lower item, whichever is greater.
%
% For the items in each horizontal location, the procedure is as follows:
% \begin{enumerate}
%     \item Place the items appearing in a given horizontal location on the page into a list.
%     \item Set the vertical shift of each item to the one specified by \key{yshift}.
%     \item For each \keyvalue{type}{optfixed} item, if it clashes with any \keyvalue{type}{fixed} item, delete it from
%           the list of items that appear on the page.
%     \item Sort the list by the position of the call to \cs{marginalia}, top-to-bottom, left-to-right, breaking ties
%           by the order of calls. (Because of floats, footnotes, etc., the sorted order of the list is not necessarily
%           the same as the order of appearance of \cs{marginalia} commands in the source code.)
%     \item Pass through the list of items in sorted order. For each \keyvalue{type}{normal} item, if necessary shift it
%           in a negative (downward) direction so that it
%           \begin{enumerate*}[label={(\arabic*)}]
%             \item does not reach closer to the top of the page than specified by \texttt{ysep page top}, and
%             \item does not clash with the previous (above) item.
%           \end{enumerate*}
%           (After this stage, it is possible for an assigned vertical shift to push a \keyvalue{type}{normal} item off
%           the bottom of the page.)
%     \item Pass through the list of items in the reverse of the sorted order. For each \keyvalue{type}{normal} item, if
%           necessary shift it in a positive (upward) direction so that it
%           \begin{enumerate*}[label={(\arabic*)}]
%             \item does not reach closer to the bottom of the page than specified by \texttt{ysep page bottom}, and
%             \item does not clash with the next (below) item.
%           \end{enumerate*}
% \end{enumerate}
% During this process, it may be found that it is impossible to prevent clashes or items reaching beyong the limits
% (e.g. fixed items clash with each other; a fixed item conflicts with \texttt{ysep page top} or \texttt{ysep page
% bottom}, or there are simply too many items of marginal content to fit (in which case, the top of some of them will be
% above the limit specified by \texttt{ysep page top} or will clash with fixed items)). In these cases, warnings are
% issued at the end of the Lua\LaTeX\ run.
%
%
%
% \section{Usage notes}
% \label{sec:usage}
%
% \pkg{marginalia} requires a minimum of two Lua\LaTeX\ runs, and often more, to place items of marginal content
% correctly. On the first pass, information about items, including their vertical size, is written to the \file{.aux}
% file, and this information is used to position them correctly on the next run. However, because \key{width} and
% \key{style} have variants dependent on the margin in which the item is placed, an item may only be typeset at the
% correct size on this second run. Thus the vertical size of the item may have changed and so the information written to
% the \file{.aux} file on the previous run may be out of date. In this case a third run may be needed for correct
% placement.
%
% More runs may be needed if the position of the call to \cs{marginalia} changes between runs. Provided the main text
% stabilizes, the placement of items using \cs{marginalia} should be correct two runs later.
%
% At the end of the Lua\LaTeX\ run, \pkg{marginalia} reports any problems encountered in the vertical placement of items
% (as decribed at the end of \fullref{Subsection}{subsec:vertical-placement}). These problems are based on calculations
% made on the basis of information from the previous written to the \file{.aux} file on the previous run, and may not
% arise if item positions or sizes (i.e. height or depth) have changed. \pkg{marginalia} also reports any changes in
% positions or sizes compared to the previous run.
%
% In these reports, a page number refers to a visible page number if it is prefixed with `\texttt{p}'; it otherwise
% refers to the absolute page number of the output.
%
%
%
% \section{Incompatibilities}
%
% Using \pkg{marginalia} alongside \cs{marginpar} or packages like \pkg{mparhak}, \pkg{marginnote}, \pkg{marginfix}, or
% \pkg{marginfit} should not produce any errors, but \pkg{marginalia} will ignore marginal content not created using
% \cs{marginalia}; for example, an item of marginal content created using \cs{marginalia} might overlap with one created
% using \cs{marginpar}.
%
%
%
% \section{Limitations}
% \label{sec:limitations}
%
% As noted in the introduction, \pkg{marginalia} was originally written to typeset a particular kind of book. It thus
% has several limitations. Three of these are:
% \begin{description}
%     \item[Lua\LaTeX only] Most of the code for deciding the placement of items of marginal content is written in Lua.
%           In principle, the it could be replaced with a pure \LaTeX\ solution.
%     \item[No support for `moving past' fixed items] The adjustment of vertical positions will never cause a
%           \keyvalue{type}{normal} item to be shifted past a \keyvalue{type}{fixed} one, even when there is space on
%           the other side. It may be desirable to have this available as an option.
%     \item[No support for nested content items] Nesting might be desirable for typesetting editions of manuscripts
%           which sometimes contain marginal glosses, and then glosses upon those glosses.
% \end{description}
%
% The lack of any built-in facility for producing (for example) numbered sidenotes is a conscious design choice. This is
% properly the concern of a command that merely uses \cs{marginalia} to place the notes correctly.
%
%
%
% ^^A\bibliography{\jobname}
% ^^A\bibliographystyle{alphaabbrv}
%
% \begin{thebibliography}{Cai24}
%
% \bibitem[Bri04]{bringhurst_elements}
% R.~Bringhurst.
% \newblock {\em {T}he {E}lements of {T}ypographic {S}tyle}.
% \newblock Hartley {\&} Marks, version 3.0, 2004.
%
% \bibitem[Cai24]{cain_formandnumber_ebook_large}
% A.~J. Cain.
% \newblock {\em {F}orm {\&} {N}umber: {A} {H}istory of {M}athematical {B}eauty}.
% \newblock Lisbon, 2024.
% \newblock {\sc url:}
%   \href{https://archive.org/details/cain_formandnumber_ebook_large}{\nolinkurl{https://archive.org/details/cain_formandnumber_ebook_large}}.
%
% \end{thebibliography}
%
%
%
% \end{documentation}
%
%
%
% \iffalse
%<*example>
\documentclass[11pt]{article}

\usepackage{marginalia}

\begin{document}

Here is some body text.\marginalia{Here is a marginal note.} Some more
body text.\marginalia[style=\footnotesize\itshape\raggedright]{Here is another
  marginal note, set in smaller text and italics, whose position has been been
  adjusted automatically.}

Some final body text.\marginalia[pos=left, valign=b, style=\sffamily\raggedleft,
width=35mm]{This note is placed on the left side of the page, wider, in sans
  serif, ragged left, and bottom-aligned.}

\end{document}
%</example>
% \fi
%
%
%
% \clearpage
% \begin{implementation}
%
%
%
% \section{Implementation (\LaTeX\ package)}
%
%    \begin{macrocode}
%<*package>
%<@@=marginalia>
%    \end{macrocode}
%
%
%
% \subsection{Initial set-up}
%
% Package identification/version information.
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}[2020-02-02]
\ProvidesExplPackage{marginalia}{2025-02-18}{0.80.2}
  {Non-floating marginal content for LuaLaTeX}
%    \end{macrocode}
% Check that Lua\TeX\ is in use.
%    \begin{macrocode}
\sys_if_engine_luatex:F
  {
    \msg_new:nnn{marginalia}{lualatex_required}
      {LuaLaTeX~required.~Package~loading~will~abort.}
    \msg_critical:nn{marginalia}{lualatex_required}
  }
%    \end{macrocode}
%
%
%
% \subsection{Options}
%
% Set up the key--value options and the variables in which the settings will be stored.
%
%
%
% \subsubsection{Type}
%
% \begin{macro}{
%   \l_@@_type_int,
% }
%   A key to store the type of the marginal content item. The setting is held in an integer variable:
%   \(1 = \key{normal}\), \(2 = \key{fixed}\), \(3 = \key{optfixed}\).
%    \begin{macrocode}
\int_new:N\l_@@_type_int
\keys_define:nn { marginalia }
{
  type .choices:nn = {normal,fixed,optfixed}{
    \int_set:Nn\l_@@_type_int{\l_keys_choice_int}
  },
  type .initial:n = normal,
}
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Horizontal placement}
%
% \begin{macro}{
%   \l_@@_pos_int,
% }
%   A key to store the specified position of the marginal content item. The setting is held in an integer variable:
%   \(1 = \key{auto}\), (the outer margin in one-column mode; left margin in left column, right margin in right column
%   in two-column mode) \(2 = \key{reverse}\) (inner margin in one-column mode; between the columns in two-column mode),
%   \(3 = \key{left}\), \(4 = \key{right}\), \(5 = \key{nearest}\).
%    \begin{macrocode}
\int_new:N\l_@@_pos_int
\keys_define:nn { marginalia }
{
  pos .choices:nn = {auto,reverse,left,right,nearest}{
    \int_set:Nn\l_@@_pos_int{\l_keys_choice_int}
  },
  pos .initial:n = auto
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \l_@@_column_int,
% }
%   A key to force the marginal content item to be treated in one-column mode or as being set from the left or right
%   column. The setting is held in an integer variable: \(-1 = \key{auto}\) (automatic), \(0 = \key{one}\) (one-column
%   mode), \(1 = \key{left}\) (left column) \(2 = \key{right}\) (right column).
%    \begin{macrocode}
\int_new:N\l_@@_column_int
\keys_define:nn { marginalia }
{
  column .choices:nn = {auto,one,left,right}{
    \int_set:Nn\l_@@_column_int{\l_keys_choice_int-2}
  },
  column .initial:n = auto,
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \l_@@_xsep_recto_outer_dim,
%   \l_@@_xsep_recto_inner_dim,
%   \l_@@_xsep_verso_outer_dim,
%   \l_@@_xsep_verso_inner_dim,
%   \l_@@_xsep_right_between_dim,
%   \l_@@_xsep_left_between_dim,
% }
%   Dimension keys to hold the separation between the marginal content item and the main text, which can be dependent on
%   where it appears on the page.
%    \begin{macrocode}
\keys_define:nn { marginalia }
{
  xsep~recto~outer .dim_set:N = \l_@@_xsep_recto_outer_dim,
  xsep~recto~inner .dim_set:N = \l_@@_xsep_recto_inner_dim,
  xsep~verso~outer .dim_set:N = \l_@@_xsep_verso_outer_dim,
  xsep~verso~inner .dim_set:N = \l_@@_xsep_verso_inner_dim,
  xsep~right~between .dim_set:N = \l_@@_xsep_right_between_dim,
  xsep~left~between .dim_set:N = \l_@@_xsep_left_between_dim,
  xsep .code:n = {
    \keys_set:nn{ marginalia }{
      xsep~recto~outer=#1,
      xsep~recto~inner=#1,
      xsep~verso~outer=#1,
      xsep~verso~inner=#1,
      xsep~right~between=#1,
      xsep~left~between=#1,
    }
  },
  xsep~outer .code:n = {
    \keys_set:nn{ marginalia }{
      xsep~recto~outer=#1,
      xsep~verso~outer=#1,
    }
  },
  xsep~inner .code:n = {
    \keys_set:nn{ marginalia }{
      xsep~recto~inner=#1,
      xsep~verso~inner=#1,
    }
  },
  xsep~between .code:n = {
    \keys_set:nn{ marginalia }{
      xsep~right~between=#1,
      xsep~left~between=#1,
    }
  },
  xsep .initial:n = \marginparsep,
}
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Vertical placement}
%
% \begin{macro}{
%   \l_@@_valign_int,
% }
%   A key to store the vertical alignment of the marginal content item. The setting is held in a integer variable:
%   \(1 = \key{t}\) (aligned at the baseline of the topmost line of the item), \(2 = \key{b}\) (aligned at the baseline
%   of the bottommost line of the item).
%    \begin{macrocode}
\int_new:N\l_@@_valign_int
\keys_define:nn { marginalia }
{
  valign .choices:nn = {t,b}{
    \int_set_eq:NN\l_@@_valign_int\l_keys_choice_int
  },
  valign .initial:n = t,
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \l_@@_default_yshift_dim,
% }
%   Dimension key to hold the default vertical shift of the marginal content item from its natural position.
%    \begin{macrocode}
\keys_define:nn { marginalia }
{
  yshift .dim_set:N = \l_@@_default_yshift_dim,
  yshift .initial:n = 0pt,
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \l_@@_ysep_above_dim,
%   \l_@@_ysep_below_dim,
%   \l_@@_ysep_page_top_dim,
%   \l_@@_ysep_page_bottom_dim
% }
%   Dimension keys to hold the the minimum vertical spacing between a marginal content item and (respectively) the item
%   above, the item below, the page top, and the page bottom.
%    \begin{macrocode}
\keys_define:nn { marginalia }
{
  ysep~above .dim_set:N = \l_@@_ysep_above_dim,
  ysep~below .dim_set:N = \l_@@_ysep_below_dim,
  ysep~page~top .dim_set:N = \l_@@_ysep_page_top_dim,
  ysep~page~bottom .dim_set:N = \l_@@_ysep_page_bottom_dim,
  ysep .code:n = {
    \keys_set:nn{ marginalia }{
      ysep~below=#1,
      ysep~above=#1,
      ysep~page~top=#1,
      ysep~page~bottom=#1,
    }
  },
  ysep .initial:n = \marginparpush,
}
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Appearance}
%
% \begin{macro}{
%   \l_@@_width_recto_outer_dim,
%   \l_@@_width_recto_inner_dim,
%   \l_@@_width_verso_outer_dim,
%   \l_@@_width_verso_inner_dim,
%   \l_@@_width_right_between_dim,
%   \l_@@_width_left_between_dim,
% }
%   Dimension keys to hold the width of the marginal content item, which can be dependent on where it appears on the
%   page.
%    \begin{macrocode}
\keys_define:nn { marginalia }
{
  width~recto~outer .dim_set:N = \l_@@_width_recto_outer_dim,
  width~recto~inner .dim_set:N = \l_@@_width_recto_inner_dim,
  width~verso~outer .dim_set:N = \l_@@_width_verso_outer_dim,
  width~verso~inner .dim_set:N = \l_@@_width_verso_inner_dim,
  width~right~between .dim_set:N = \l_@@_width_right_between_dim,
  width~left~between .dim_set:N = \l_@@_width_left_between_dim,
  width .code:n = {
    \keys_set:nn{ marginalia }{
      width~recto~outer=#1,
      width~recto~inner=#1,
      width~verso~outer=#1,
      width~verso~inner=#1,
      width~right~between=#1,
      width~left~between=#1,
    }
  },
  width~outer .code:n = {
    \keys_set:nn{ marginalia }{
      width~recto~outer=#1,
      width~verso~outer=#1,
    }
  },
  width~inner .code:n = {
    \keys_set:nn{ marginalia }{
      width~recto~inner=#1,
      width~verso~inner=#1,
    }
  },
  width~between .code:n = {
    \keys_set:nn{ marginalia }{
      width~right~between=#1,
      width~left~between=#1,
    }
  },
  width .initial:n = \marginparwidth,
}
%    \end{macrocode}
% \end{macro}
%
%% \begin{macro}{
%   \l_@@_style_recto_outer_tl,
%   \l_@@_style_recto_inner_tl,
%   \l_@@_style_verso_outer_tl,
%   \l_@@_style_verso_inner_tl,
%   \l_@@_style_right_between_tl,
%   \l_@@_style_left_between_tl,
% }
%   Token list keys to hold the style with which a marginal content item is typeset, which can be dependent on where it
%   appears on the page.
%    \begin{macrocode}
\keys_define:nn { marginalia }
{
  style~recto~outer .tl_set:N = \l_@@_style_recto_outer_tl,
  style~recto~inner .tl_set:N = \l_@@_style_recto_inner_tl,
  style~verso~outer .tl_set:N = \l_@@_style_verso_outer_tl,
  style~verso~inner .tl_set:N = \l_@@_style_verso_inner_tl,
  style~right~between .tl_set:N = \l_@@_style_right_between_tl,
  style~left~between .tl_set:N = \l_@@_style_left_between_tl,
  style .code:n = {
    \keys_set:nn{ marginalia }{
      style~recto~outer=#1,
      style~recto~inner=#1,
      style~verso~outer=#1,
      style~verso~inner=#1,
      style~right~between=#1,
      style~left~between=#1,
    }
  },
  style .initial:n = {},
}
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsection{Lua backend and interface}
%
% Load the Lua backend.
%    \begin{macrocode}
  \lua_now:n{
    marginalia = require('marginalia')
  }
%    \end{macrocode}
%
% The following 9 macros interface between \LaTeX\ and Lua code. Each control sequence \cs[no-index]{@@_lua_XYZ}
% simply calls the corresponding Lua function \luafunc{marginalia.XYZ}.
% \begin{macro}{
% \@@_lua_store_default_page_data:,
% \@@_lua_store_page_data:n,
% \@@_lua_check_page_data:n,
% \@@_lua_store_item_data:n,
% \@@_lua_check_item_data:n,
% \@@_lua_compute_items:,
% \@@_lua_write_problem_report:,
% \@@_lua_write_item_change_report:,
% }
%   The first 8 macros do not require expansion of parameters: they either have none, or process data not containing
%   control sequences (read from the \file{.aux} file); hence \cs{lua_now:n} is used.
%    \begin{macrocode}
\cs_new:Npn\@@_lua_store_default_page_data:
  {
    \lua_now:n{ marginalia.store_default_page_data() }
  }
\cs_new:Npn\@@_lua_store_page_data:n #1
  {
    \lua_now:n{ marginalia.store_page_data('#1') }
  }
\cs_new:Npn\@@_lua_check_page_data:n #1
  {
    \lua_now:n{ marginalia.check_page_data('#1') }
  }
\cs_new:Npn\@@_lua_write_page_change_report:
  {
    \lua_now:n{ marginalia.write_page_change_report() }
  }
\cs_new:Npn\@@_lua_store_item_data:n #1
  {
    \lua_now:n{ marginalia.store_item_data('#1') }
  }
\cs_new:Npn\@@_lua_check_item_data:n #1
  {
    \lua_now:n{ marginalia.check_item_data('#1') }
  }
\cs_new:Npn\@@_lua_compute_items:
  {
    \lua_now:n{ marginalia.compute_items() }
  }
\cs_new:Npn\@@_lua_write_problem_report:
  {
    \lua_now:n{ marginalia.write_problem_report() }
  }
\cs_new:Npn\@@_lua_write_item_change_report:
  {
    \lua_now:n{ marginalia.write_item_change_report() }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \@@_lua_load_item_data:n,
% }
%   The last macro will receive a control sequence parameter and so requires expansion; hence
%   \cs{lua_now:e} is used.
%    \begin{macrocode}
\cs_new:Npn\@@_lua_load_item_data:n #1
  {
    \lua_now:e{ marginalia.load_item_data('#1') }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsection{Processing data from the \texorpdfstring{\file{.aux}}{.aux} file}
%
% \begin{macro}[int]{
%   \marginalia@pagedata,
% }
%   This command is used to store page data in the \file{.aux} file.
%    \begin{macrocode}
\NewDocumentCommand{\marginalia@pagedata}{ m }{
  \@@_process_page_data:n{#1}
}
%    \end{macrocode}
%   Initially \cs{@@_process_page_data:n} is set to \cs{@@_lua_store_page_data:n}. Thus, when the \file{.aux} file is
%   read, \cs{marginalia@pagedata} will pass the page data to the Lua backend to be stored.
%    \begin{macrocode}
\cs_set_eq:NN
  \@@_process_page_data:n
  \@@_lua_store_page_data:n
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{
%   \marginalia@itemdata,
% }
%   This command is used to store data for each marginal content item in the \file{.aux} file.
%    \begin{macrocode}
\DeclareDocumentCommand{\marginalia@itemdata}{ m }{
  \@@_process_item_data:n{#1}
}
%    \end{macrocode}
% \end{macro}
% Initially \cs{@@_process_item_data:n} is set to \cs{@@_lua_store_item_data:n}. Thus, when the \file{.aux} file is
% read, \cs{marginalia@itemdata} will pass the item data to the Lua backend to be stored.
%    \begin{macrocode}
\cs_set_eq:NN
  \@@_process_item_data:n
  \@@_lua_store_item_data:n
%    \end{macrocode}
% At the \texttt{begindocument} hook, the \file{.aux} file has been read and closed. The Lua backend now stores the
% geometry and computes the vertical shift for each item. Then the handle for the main \file{.aux} file is stored for
% use in this package.
%    \begin{macrocode}
\AddToHook{begindocument}{
  \@@_lua_store_default_page_data:
  \@@_lua_compute_items:
  \cs_set_eq:NN\l_@@_aux_iow\@mainaux
}
%    \end{macrocode}
% The \texttt{enddocument/afterlastpage} hook is before the \file{.aux} file is read back, so this is where
% \cs{@@_process_page_data:n} and \cs{@@_process_item_data:n} are set, respectively, to \cs{@@_lua_check_page_data:n}
% and \cs{@@_lua_check_item_data:n}. Thus, when the \file{.aux} file is read back, \cs{marginalia@pagedata} and
% \cs{marginalia@itemdata} will pass data to the Lua backend to be checked for changes.
%    \begin{macrocode}
\AddToHook{enddocument/afterlastpage}{
  \cs_set_eq:NN
    \@@_process_page_data:n
    \@@_lua_check_page_data:n
  \cs_set_eq:NN
    \@@_process_item_data:n
    \@@_lua_check_item_data:n
  }
%    \end{macrocode}
% \begin{macro}{\@@_write_reports:}
%   All the reports of changes and/or problems are assembled in the Lua backend. This macro will write the reports as
%   package warnings, using the following three messages, to which the Lua-assembled reports are passed as parameters:
%    \begin{macrocode}
\msg_new:nnn{marginalia}{placement_problem}
  { Problems~in~placement.~#1 }
\msg_new:nnn{marginalia}{item_change}
  { Changes~in~item~data.~#1 }
\msg_new:nnn{marginalia}{page_change}
  { Changes~in~page~data.~#1 }
\cs_new:Npn\@@_write_reports:
  {
    \group_begin:
    \tl_set:Ne\l_tmpa_tl{\@@_lua_write_problem_report:}
    \tl_if_blank:VF\l_tmpa_tl
      {
        \msg_warning:nne{marginalia}{placement_problem}{\tl_use:N\l_tmpa_tl}
      }
    \tl_set:Ne\l_tmpa_tl{\@@_lua_write_item_change_report:}
    \tl_if_blank:VF\l_tmpa_tl
      {
        \msg_warning:nne{marginalia}{item_change}{\tl_use:N\l_tmpa_tl}
      }
    \tl_set:Ne\l_tmpa_tl{\@@_lua_write_page_change_report:}
    \tl_if_blank:VF\l_tmpa_tl
      {
        \msg_warning:nne{marginalia}{page_change}{\tl_use:N\l_tmpa_tl}
      }
    \group_end:
  }
%    \end{macrocode}
% \end{macro}
% Use the \texttt{enddocument/info} hook to write the reports of changes and/or problems.
%    \begin{macrocode}
\AddToHook{enddocument/info}{
  \@@_write_reports:
}
%    \end{macrocode}
%
%
%
% \subsection{Writing page data to the \texorpdfstring{\file{.aux}}{.aux} file}
%
% To compute the positions of marginal content items, certain page layout data is required. And since all the
% computation takes place at the beginning of the document, it is necessary to write this information to the \file{.aux}
% file.
%
% \begin{macro}{\g_@@_pagedatano_int}
%   Global integer variable to index page data items written to the \file{.aux} file.
%    \begin{macrocode}
\int_new:N\g_@@_pagedatano_int
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_write_page_data}
%   This command will be used to write the current page data to the \file{.aux} file. It is initially defined to do
%   nothing, so that the use of \cs{marginalianewgeometry} in the preamble does not cause errors (because the
%   \file{.aux} file is not available for writing until \texttt{begindocument/end}).
%    \begin{macrocode}
\cs_set_eq:NN\@@_write_page_data:\prg_do_nothing:
\cs_new:Npn\@@_write_page_data_real:
  {
    \int_gincr:N\g_@@_pagedatano_int
    \iow_now:Ne\l_@@_aux_iow{
      \token_to_str:N\marginalia@pagedata{
        pagedatano=\int_value:w\g_@@_pagedatano_int,
        abspageno=\int_eval:n{\g_shipout_readonly_int+1},
        hoffset=\int_value:w\hoffset,
        voffset=\int_value:w\voffset,
        paperheight=\int_value:w\paperheight,
        oddsidemargin=\int_value:w\oddsidemargin,
        evensidemargin=\int_value:w\evensidemargin,
        textwidth=\int_value:w\textwidth,
        columncount=\int_value:w\col@number,
        columnwidth=\int_value:w\columnwidth,
        columnsep=\int_value:w\columnsep,
        twoside=\bool_to_str:n{\legacy_if_p:n{@twoside}},
      }
    }
  }
%    \end{macrocode}
%   At the \texttt{begindocument/end} hook, the \file{.aux} file has been opened for writing, and so the macro
%   \cs{@@_write_page_data:} is enabled and the initial page data is written out.
%    \begin{macrocode}
\AddToHook{begindocument/end}
  {
    \cs_set_eq:NN
      \@@_write_page_data:
      \@@_write_page_data_real:
    \@@_write_page_data:
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsection{Marginal content item processing}
%
% \subsubsection{Variables}
%
% \paragraph{Variables set by \LaTeX.}
%
% \begin{macro}{\g_@@_itemno_int}
%   Global integer variable to index marginal content items.
%    \begin{macrocode}
\int_new:N\g_@@_itemno_int
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\l_@@_item_box}
%   Box variable to hold the typeset marginal content item.
%    \begin{macrocode}
\box_new:N\l_@@_item_box
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
% \l_@@_item_height_dim,
% \l_@@_item_depth_dim,
% }
%   Dimension variables to hold the height and depth of the typeset margin content item.
%    \begin{macrocode}
\dim_new:N\l_@@_item_height_dim
\dim_new:N\l_@@_item_depth_dim
%    \end{macrocode}
% \end{macro}
%
%
%
% \paragraph{Variables set by Lua.}
%
% The following variables will be set by the Lua backend via \texttt{tex.count} and \texttt{tex.dimen} when
% \cs{@@_lua_load_item_data:n} is called.
%
% \begin{macro}{\l_@@_page_int}
%   Integer variable for the page on which the marginal content item appears. This variable will be
%   made available via \cs{marginaliapage} within the \meta{content} of \cs{marginalia}.
%    \begin{macrocode}
\int_new:N\l_@@_page_int
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\l_@@_column_computed_int}
%   Integer variable for the column next to which the marginal content item appears. This variable will be
%   will be made available via \cs{marginaliacolumn} within the \meta{content} of \cs{marginalia}.
%    \begin{macrocode}
\int_new:N\l_@@_column_computed_int
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
% \l_@@_xshift_computed_dim,
% \l_@@_yshift_computed_dim,
% }
%   Dimension variables to hold the differences in \(x\) and \(y\) coordinates between the call to \cs{marginalia} and
%   the position where the marginal content item should appear.
%    \begin{macrocode}
\dim_new:N\l_@@_xshift_computed_dim
\dim_new:N\l_@@_yshift_computed_dim
%    \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{\l_@@_side_computed_int}
%   Integer variable to indicate the side of the text block or column on which the marginal content item should be
%   placed: \(0 = \textrm{right}\) and \(1 = \textrm{left}\).
%    \begin{macrocode}
\int_new:N\l_@@_side_computed_int
%    \end{macrocode}
%   (This variable could be a boolean, but an integer is used because there is no canonical access to booleans from
%   Lua.)
% \end{macro}
%
% \begin{macro}{\l_@@_marginno_computed_int}
%   Integer variable to indicate in which margin the content will be be placed, to enable quick selection of width and
%   style: \(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}
\int_new:N\l_@@_marginno_computed_int
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\l_@@_enabled_computed_int}
%   Integer variable to indicate whether the marginal content item is enabled: \(0 = \textrm{disabled}\),
%   \(1 = \textrm{enabled}\).
%    \begin{macrocode}
\int_new:N\l_@@_enabled_computed_int
%    \end{macrocode}
%   (This variable could be a boolean, but an integer is used because there is no canonical access to booleans from
%   Lua.)
% \end{macro}
%
%
%
% \subsubsection{Core macro}
%
% \begin{macro}{\@@_process_item:nn}
%   This macro does most of the work in setting the marginal content item. The first parameter is \meta{options}, the
%   second is \meta{content}.
%    \begin{macrocode}
\cs_new:Npn\@@_process_item:nn #1#2
  {
%    \end{macrocode}
%   First, increment the index, then enter a group where all the action will happen.
%    \begin{macrocode}
    \int_gincr:N\g_@@_itemno_int
    \group_begin:
%    \end{macrocode}
%   Process \meta{options}. These settings apply locally inside the group.
%    \begin{macrocode}
      \keys_set:nn{marginalia}{ #1 }
%    \end{macrocode}
%   Get item data from the Lua backend: the integer variables \cs{l_@@_page_int}, \cs{l_@@_column_computed_int},
%   \cs{l_@@_side_computed_int}, \cs{l_@@_enabled_computed_int}, and the dimension variables
%   \cs{l_@@_xshift_computed_dim}, and \cs{l_@@_yshift_computed_dim} are set by Lua via \texttt{tex.count} and
%   \texttt{tex.dimen}. If no data is available (if, for instance, no data has been stored from a previous run), default
%   values will be set by Lua. On later runs, the Lua backend will supply the values computed from the data written to
%   the \file{.aux} file on the previous run.
%    \begin{macrocode}
      \@@_lua_load_item_data:n
        { \int_value:w\g_@@_itemno_int }
%    \end{macrocode}
%   Choose the correct auxiliary function for typesetting, depending on which mode \TeX\ is in.
%    \begin{macrocode}
      \mode_if_math:TF
        {
          \cs_set_eq:NN
            \@@_typeset:n
            \@@_typeset_mmode:n
        }
        {
          \legacy_if:nT{@inlabel}
            { \leavevmode }
          \mode_if_horizontal:TF
            {
              \cs_set_eq:NN
                \@@_typeset:n
                \@@_typeset_hmode:n
            }
            {
              \cs_set_eq:NN
                \@@_typeset:n
                \@@_typeset_vmode:n
            }
        }
%    \end{macrocode}
%   Choose the correct box in which to typeset the item. \cs{l_@@_valign_int} can only be \(1\) or \(2\), so take \(2\)
%   to signify bottom-aligned, anything else signifies top-aligned.
%    \begin{macrocode}
      \int_compare:nNnTF{\l_@@_valign_int}={2}
        {
          \cs_set_eq:NN\@@_item_box_set:Nn\vbox_set:Nn
        }
        {
          \cs_set_eq:NN\@@_item_box_set:Nn\vbox_set_top:Nn
        }
%    \end{macrocode}
%   Choose the correct horizontal separation, width, and style for the item.
%    \begin{macrocode}
      \@@_set_xsep_width_style:
%    \end{macrocode}
%   Typeset the \meta{content} into \cs{l_@@_item_box}. Use \cs{@parboxrestore} for brevity, even though \cs{hsize} and
%   \cs{linewidth} are subsequently set to \cs{l_@@_width_dim}. Make available \cs{marginaliapage} and
%   \cs{marginaliacolumn}.
%    \begin{macrocode}
      \@@_item_box_set:Nn\l_@@_item_box{
        \@parboxrestore
        \normalfont\normalsize

        \tl_use:N\l_@@_style_tl
        \dim_set_eq:NN\hsize\l_@@_width_dim
        \dim_set_eq:NN\linewidth\hsize

        \cs_set_eq:NN\marginaliapage\l_@@_page_int
        \cs_set_eq:NN\marginaliacolumn\l_@@_column_computed_int

        \group_begin:
        \ignorespaces
        #2
        \par
        \group_end:
      }
%    \end{macrocode}
%   Measure \cs{l_@@_item_box}.
%    \begin{macrocode}
      \dim_set:Nn\l_@@_item_height_dim
        {\box_ht:N\l_@@_item_box}
      \dim_set:Nn\l_@@_item_depth_dim
        {\box_dp:N\l_@@_item_box}
%    \end{macrocode}
%   Everything is now ready to place the item on the page and write the necessary data to the \file{.aux} file. Use the
%   chosen auxiliary function for typesetting, and immediately use \cs{savepos} to store the callout position.
%    \begin{macrocode}
      \@@_typeset:n{
        \savepos
%    \end{macrocode}
%   Write the item data to the \file{.aux} file. All tokens that will change for future items, and which are currently
%   meaningful, are expanded now; the remainder will be expanded at shipout time, when \emph{they} are meaningful.
%    \begin{macrocode}
        \iow_shipout_e:Ne\l_@@_aux_iow{
          \token_to_str:N\marginalia@itemdata{
            itemno=\int_value:w\g_@@_itemno_int,
            abspageno=\exp_not:N\int_eval:n{\g_shipout_readonly_int},
            pageno=\exp_not:N\int_value:w\c@page,
            type=\str_use:N\int_value:w\l_@@_type_int,
            xpos=\exp_not:N\int_value:w\lastxpos,
            ypos=\exp_not:N\int_value:w\lastypos,
            height=\int_value:w\l_@@_item_height_dim,
            depth=\int_value:w\l_@@_item_depth_dim,
            pos=\int_value:w\l_@@_pos_int,
            column=\int_value:w\l_@@_column_int,
            yshift=\int_value:w\l_@@_default_yshift_dim,
            ysep~above=\int_value:w\l_@@_ysep_above_dim,
            ysep~below=\int_value:w\l_@@_ysep_below_dim,
            ysep~page~top=\int_value:w\l_@@_ysep_page_top_dim,
            ysep~page~bottom=\int_value:w\l_@@_ysep_page_bottom_dim,
          }
        }
%    \end{macrocode}
%   Finally, if the item is enabled, typeset it onto the page: shift the item by
%   \[
%     \abs[\big]{\cs{l_@@_xshift_computed_dim}} + \abs[\big]{\cs{l_@@_xsep_dim}}
%   \]
%   to the right in an \cs{rlap} or to the left in an \cs{llap}, depending on \cs{l_@@_side_computed_int}, then use
%   \cs{@@_place_item_box} for the vertical placement.
%    \begin{macrocode}
      \int_if_zero:nF{\l_@@_enabled_computed_int}
        {
          \int_if_zero:nTF{\l_@@_side_computed_int}
            {
              \rlap{
                \kern\l_@@_xshift_computed_dim
                \kern\l_@@_xsep_dim
                \@@_place_item_box:
              }
            }
            {
              \llap{
                \@@_place_item_box:
                \kern\l_@@_xsep_dim
                \kern-\l_@@_xshift_computed_dim
              }
            }
        }
      }
%    \end{macrocode}
%   Close the group started near the beginning of \cs{@@_process_item:nn}.
%    \begin{macrocode}
    \group_end:
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Width and style selection}
%
% \begin{macro}{\@@_set_xsep_width_style}
%   Set \cs{l_@@_xsep_dim}, \cs{l_@@_width_dim}, and \cs{l_@@_style_tl}, based on \cs{l_@@_marginno_computed_int}.
%    \begin{macrocode}
\cs_new:Npn\@@_set_xsep_width_style:
  {
    \int_case:nn{\l_@@_marginno_computed_int}
      {
        {0}
        {
          \cs_set_eq:NN\l_@@_xsep_dim
            \l_@@_xsep_recto_outer_dim
          \cs_set_eq:NN\l_@@_width_dim
            \l_@@_width_recto_outer_dim
          \cs_set_eq:NN\l_@@_style_tl
            \l_@@_style_recto_outer_tl
        }
        {1}
        {
          \cs_set_eq:NN\l_@@_xsep_dim
            \l_@@_xsep_recto_inner_dim
          \cs_set_eq:NN\l_@@_width_dim
            \l_@@_width_recto_inner_dim
          \cs_set_eq:NN\l_@@_style_tl
            \l_@@_style_recto_inner_tl
        }
        {2}
        {
          \cs_set_eq:NN\l_@@_xsep_dim
            \l_@@_xsep_verso_outer_dim
          \cs_set_eq:NN\l_@@_width_dim
            \l_@@_width_verso_outer_dim
          \cs_set_eq:NN\l_@@_style_tl
            \l_@@_style_verso_outer_tl
        }
        {3}
        {
          \cs_set_eq:NN\l_@@_xsep_dim
            \l_@@_xsep_verso_inner_dim
          \cs_set_eq:NN\l_@@_width_dim
            \l_@@_width_verso_inner_dim
          \cs_set_eq:NN\l_@@_style_tl
            \l_@@_style_verso_inner_tl
        }
        {4}
        {
          \cs_set_eq:NN\l_@@_xsep_dim
            \l_@@_xsep_right_between_dim
          \cs_set_eq:NN\l_@@_width_dim
            \l_@@_width_right_between_dim
          \cs_set_eq:NN\l_@@_style_tl
            \l_@@_style_right_between_tl
        }
        {5}
        {
          \cs_set_eq:NN\l_@@_xsep_dim
            \l_@@_xsep_left_between_dim
          \cs_set_eq:NN\l_@@_width_dim
            \l_@@_width_left_between_dim
          \cs_set_eq:NN\l_@@_style_tl
            \l_@@_style_left_between_tl
        }
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Auxiliary placement macros}
%
% \begin{macro}{\@@_place_item_box:}
%   Place the item that has been set in \cs{l_@@_item_box}, vertically shifted by \cs{l_@@_yshift_computed_dim} and
%   \cs{smash}ed to avoid altering vertical spacing in the main text.
%    \begin{macrocode}
\cs_new:Npn\@@_place_item_box:
  {
    \smash
      {
        \box_move_up:nn{\l_@@_yshift_computed_dim}
          {
            \box_use:N\l_@@_item_box
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}{
%   \@@_typeset_mmode:n,
%   \@@_typeset_hmmode:n,
%   \@@_typeset_vmode:n,
% }
%   These three macros handle typsetting in math mode, horizontal mode, and vertical mode. Nothing special needs to be
%   done in math mode. In horizontal mode, \cs{@bsphack}\ldots\cs{@bsphack} avoids double spacing. In vertical mode, a
%   new paragraph containing only a \cs{strut} is started, the item is typeset, the paragraph is ended, and then a
%   vertical skip of \(-\cs{baselineskip}\) should `hide' that invisible paragraph.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_mmode:n #1
  {
    #1
  }
\cs_new:Npn\@@_typeset_hmode:n #1
  {
    \@bsphack
    #1
    \@esphack
  }
\cs_new:Npn\@@_typeset_vmode:n #1
  {
    \nobreak\noindent\strut #1\par
    \skip_vertical:n{-\baselineskip}
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsection{User commands}
%
% Finally, set up the commands for the user.
%
% \begin{macro}{\marginalia}
%   This is the main user command for creating a marginal content item. This macro does nothing but hand off to
%   \cs{@@_process_item:nn}.
%    \begin{macrocode}
\NewDocumentCommand{\marginalia}{ O{} +m }
  {
    \@@_process_item:nn{#1}{#2}
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}{\marginaliasetup}
%   The user command to set the configuration.
%    \begin{macrocode}
\NewDocumentCommand{\marginaliasetup}{ m }
{
  \keys_set:nn{marginalia}{ #1 }
}
%    \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}{\marginalianewgeometry}
%   The user command to signal that the page geometry has been changed.
%    \begin{macrocode}
\NewDocumentCommand{\marginalianewgeometry}{}
{
  \@@_write_page_data:
}
%    \end{macrocode}
% \end{macro}
%
%
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
%
%
% \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}
%
%
%
% \clearpage
% \end{implementation}
