% \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.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}}%
}


\numberwithin{figure}{section}
\numberwithin{table}{section}



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



\newcommand*\key[1]{\texttt{#1}}
\newcommand*\val[1]{\texttt{#1}}
\newcommand*\keyval[2]{\texttt{#1=#2}}
\newcommand*\ttdashdash{\texttt{-}\texttt{-}\hskip 0pt\relax}


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


\newcommand*\mcode[1]{\texttt{#1}}


\newcommand*\param[1]{\texttt{\##1}}


\newcommand*\MakeIndex{\textit{MakeIndex}}
\newcommand*\upmendex{\texttt{upmendex}}
\newcommand*\xindy{\texttt{xindy}}


\usepackage{siunitx}
\sisetup{
  mode=match,
}
\DeclareSIUnit\point{pt}
\DeclareSIUnit\spoint{sp}


\newcommand*\purl[1]{\textsc{url:}~\url{#1}}


\usepackage{listings}



\usepackage{indextra}



\begin{document}
\DocInput{indextra.dtx}
\end{document}
%</driver>
% \fi
%
%
%
% \GetFileInfo{indextra.sty}
%
% \makeatletter
% \lstset{
%   language=[LaTeX]TeX,
%   columns=fullflexible,
%   keepspaces=true,
%   commentstyle=\slshape,
%   basicstyle=\ttfamily\lst@ifdisplaystyle\small\fi,
% }
% \makeatother
%
%
%
% \title{^^A
% \pkg{indextra} ^^A
%   --- Enhanced index typesetting^^A
%   \thanks{This file describes \fileversion, last revised \filedate.}^^A
% }
%
% \author{^^A
%  Alan J. Cain\thanks{Email:~\texttt{a.j.cain (AT) gmail.com}}^^A
% }
%
% \date{Released \filedate}
%
% \maketitle
%
%
%
% \begin{abstract}
%   This package provides some enhanced features for typesetting indexes, notably: (1) Continuation text when entries or
%   sub-entries continue from one page or column to the next. (2) An interface for accessing marks created from index
%   entries, so that (for example) a running head can include the range of index entries that appears on the page.
% \end{abstract}
%
%
%
% \tableofcontents
%
%
%
% \begin{documentation}
%
% \section{Introduction}
%
% By default, an index in a \LaTeX\ document is typeset in two columns and provides no indication if a page or column
% break occurs inside an index entry or sub-entry. The package
% \pkg{repeatindex}\footnote{\purl{https://ctan.org/pkg/repeatindex}} handles a page or column break that occurs between
% sub-entries within the same entry by repeating the keyword of the main entry. But it does not repeat the keyword of a
% sub-entry when a page or column break occurs within it. Nor does it repeat the keyword of an entry (at the top level)
% % if the break occurs mid-way through a long list of pages or ranges for that entry directly.
% In \emph{The \TeX book}, Knuth described a plain \TeX\ method to create continuation texts using the mark mechanism
% \cite[pp.~261--3]{knuth_texbook}, which does not have these limitations. But there are still situations in which this
% method can cause problems: for instance, if the keyword in the index entry spans two or more lines, it may break
% across a page or column and the continuation text would appear in the middle of the keyword.
%
% The \pkg{indextra} package is intended to create suitable continuation texts for all of these possibilities. An
% example is shown in \fullref{Figure}{fig:indextra-doc-demo}, where the list of locations within a sub-entry is too
% long to fit in the left-hand column. At the top of the right-hand column, \pkg{indextra} has indicated that both the
% main entry and the sub-entry have both been continued. If there had been a page break as well as a column break, the
% same indicators would have been placed at the start of the left-hand column of the next page.
%
% \begin{figure}[t]
%   \centering
%   \includegraphics{indextra-doc-demo.pdf}
%   \caption{An example of how \pkg{indextra} continues an entry and a sub-entry after a column or page break.}
%   \label{fig:indextra-doc-demo}
% \end{figure}
%
% \pkg{indextra} tries to break columns intelligently. If only one line of a multi-line index entry would fit in the
% remaining space of the current column, the column break is inserted first, since there would be no saving in space
% because the continutation text would occupy (at least) one line of the next column. Similarly, it will not break a
% column before or during any cross-references in the index entry.
%
% \pkg{indextra} also supplies a system for retrieving index entry keywords as marks, for use in running heads to show
% the range of index entries on the current page. The keywords of the first and last top-level entries on each page are
% available as \cs{indextrafirstmark} and \cs{indextralastmark}.
%
% \medskip
% \noindent\textit{Caveat:}~~Although \pkg{indextra} has been used successfully for the indexes of the author's
% books,\footnote{Available on the Internet Archive under Creative Commons licences:\par
% (1)~A.J. Cain, \textit{Form \& Number: A History of Mathematical Beauty}. Lisbon, 2024.
% \purl{https://archive.org/details/cain_formandnumber_ebook_large}\par
% (2)~G.H. Hardy, \textit{An Annotated Mathematician's Apology}. With annotations and commentary by A.J. Cain. Lisbon,
% 2019. \purl{https://archive.org/details/hardy_annotated}} in its current state it should be regarded as
% semi-experimental, requiring further development, and having many limitations and some incompatibilites (see
% \fullref{Subsection}{subsec:limitations}).
%
%
%
% \section{Requirements}
%
% \pkg{indextra} does not depend on any other packages, but requires a recent \LaTeX\ kernel with \pkg{expl3} support.
% (Any kernel version since 2020-02-02 should suffice.)
%
%
%
% \section{Installation}
%
% To install \pkg{indextra} manually, run \texttt{tex indextra.ins} and copy the file \texttt{indextra.sty} to somewhere
% \LaTeX\ can find it, \texttt{indextra.ist} to somewhere \MakeIndex\ and/or \upmendex\ can find it, and
% \texttt{indextra.xdy} to somewhere \xindy\ can find it. To build the documentation, first compile
% \texttt{indextra-doc-demo.tex} to a PDF, then compile \texttt{indextra-doc.tex}.
%
%
%
% \section{Getting started}
%
% \subsection{\LaTeX}
%
% On the \LaTeX\ side, simply use the package with \lstinline!\usepackage{indextra}!. (There are no package options.)
% Use \cs{makeindex} and \cs{index} as as usual. Note that \pkg{indextra} is incompatible with \pkg{imakeindex}.
%
% If the \pkg{hyperref} package is used, set \keyval{hyperindex}{false} in its package options or in \cs{hypersetup},
% because \pkg{indextra} has its own mechanism to add index hyperlinks, activated by its own \key{hyperindex} key.
%
%
%
% \subsection{Index generation}
%
% The \file{.ind} file (generated by \MakeIndex, \upmendex, or \xindy) must be specially formatted for \pkg{indextra}.
% The style file \file{indextra.ist} is supplied for use with \MakeIndex\ and \upmendex. It can be used with:\par
% \texttt{makeindex -s indextra.ist \meta{filename}.idx}\par
% \noindent or\par
% \texttt{upmendex -s indextra.ist \meta{filename}.idx}\par
% \noindent Also supplied is a barebones \xindy\ module \file{indextra.xdy} style file, which should be used with other
% modules to set up sorting and attributes. The module \file{indextra.xdy} should be specified last when invoking
% \xindy, to override any previous specification of how to write to the \file{.idx} file. For example, one might
% use:\par
% \texttt{xindy -M makeidx.xdy -M utf8.xdy -M indextra.xdy \meta{filename}.idx}
%
%
%
% \section{Configuration}
%
% \begin{function}{\indextrasetup}
%   \begin{syntax}
%     \cs{indextrasetup}\marg{options}
%   \end{syntax}
%   This command is used to specify options. The argument \meta{options} is a key--value list. The options are described
%   in the subsections below and are summarized in \fullref{Table}{tbl:keys-summary}.
% \end{function}
%
% \begin{table}[t]
%   \addtolength{\leftskip}{-12mm}%
%   \addtolength{\textwidth}{12mm}%
%   \caption{Summary of keys that can be set using \cs{indextrasetup}.}
%   \label{tbl:keys-summary}
%   \begin{tabular*}{\textwidth}{lll@{\extracolsep{\fill}}}
%     \toprule
%     \textsf{Key name}                 & \textsf{Value}                   & \textsf{Default}                                 \\
%     \midrule
%     \key{before code}                 & \LaTeX\ code                     & \val{\cs{begin}\{theindex\}}                     \\
%     \key{after code}                  & \LaTeX\ code                     & \val{\cs{end}\{theindex\}}                       \\
%     \key{level 0 style}               & \LaTeX\ code                     & \val{\cs{parindent}=0em\cs{hangindent}=3.75em}   \\
%     \key{level 1 style}               & \LaTeX\ code                     & \val{\cs{parindent}=1.5em\cs{hangindent}=5.25em} \\
%     \key{level 2 style}               & \LaTeX\ code                     & \val{\cs{parindent}=3em\cs{hangindent}=6.75em}   \\
%     \key{separator keyword crossref}  & \LaTeX\ code                     & \cs{break}                                       \\
%     \key{separator keyword location}  & \LaTeX\ code                     & \cs{space}\cs{space}                             \\
%     \key{separator crossref crossref} & \LaTeX\ code                     & \cs{break}                                       \\
%     \key{separator crossref location} & \LaTeX\ code                     & \cs{break}                                       \\
%     \key{separator location location} & \LaTeX\ code                     & \val{,\textvisiblespace}                         \\
%     \key{crossref macros}             & List of macros                   & \cs{see}\cs{seealso}                             \\
%     \key{hyperindex}                  & \(\set{\val{true},\val{false}}\) & \val{true}                                       \\
%     \key{bookmarks}                   & \(\set{\val{true},\val{false}}\) & \val{true}                                       \\
%     \bottomrule
%   \end{tabular*}
% \end{table}
%
%
%
% \subsection{Before and after code}
%
% \DescribeOption{before code}
% This option specifies code that is executed at the start of the index (more specifically,
% at the start of the \env{theindextra} environment in the generated \file{.ind} file). The code will be executed before
% \pkg{indextra} sets various parameters and makes available the commands described in
% \fullref{Section}{sec:ind-macros}. The user may wish to use this option to set up running heads using \pkg{indextra}'s
% marks (see \fullref{Subsection}{subsec:example-marks} for an example).  \default{\cs{begin}\mcode{\{theindex\}}}
%
% \DescribeOption{after code}
% This option specifies code that is executed at end start of the index (more specifically,
% at the end of the \env{theindextra} environment in the generated \file{.ind} file).
% \default{\cs{begin}\mcode{\{theindex\}}}
%
%
%
% \subsection{Styles}
%
% \DescribeOption{level \(n\) style} The style applied to an index entry at level \(n = 0,1,2\) (that is, respectively
% to an entry, a sub-entry, and a sub-sub-entry).
%
% \noindent
% \begin{tabular}{@{}r@{~}l}
%   (\textit{Default:}                  & \keyval{level 0 style}{\cs{parindent}=0em\allowbreak\cs{hangindent}=3.75em};        \\
%                                       & \keyval{level 1 style}{\cs{parindent}=1.5em\cs{hangindent}=5.25em};                 \\
%                                       & \keyval{level 2 style}{\cs{parindent}=3em\allowbreak\cs{hangindent}=6.75em})
% \end{tabular}
%
%
%
% \subsection{Separators}
%
% \pkg{indextra} always typesets cross-references before locations in each index entry, sub-entry, or sub-sub-entry. The
% following options specify the separators to be used.
%
% \DescribeOption{separator keyword crossref}
% The separator to be placed between the keyword of an index entry and a cross-reference. \default{\cs{break}}
%
% \DescribeOption{separator keyword location}
% The separator to be placed between the keyword of an index entry and a location (that is, a page or range of pages),
% when the first location immediately follows the keyword (when there are no cross-references).
% \default{\cs{space}\cs{space}}
%
% \DescribeOption{separator crossref crossref}
% The separator to be placed between the two cross-references in an index entry. \default{\cs{break}}
%
% \DescribeOption{separator crossref location}
% The separator to be placed between the last cross-reference in an entry and the first location. \default{\cs{break}}
%
% \DescribeOption{separator location location}
% The separator to be placed between two locations in an index entry. \default{\texttt{,\textvisiblespace}}
%
%
%
% \subsection{Specification of cross-references}
%
% \DescribeOption{crossref macros}
% A list of macros that signify cross-references. \default{\cs{see}\cs{seealso}}
%
%
%
% \subsection{Hyperlinks}
%
% \DescribeOption{hyperindex}
% A boolean \val{true}/\val{false} that indicates whether index locations should be hyperlinked if the \cs{hyperpage}
% macro from the \pkg{hyperref} package is available. Note that a user redefinition of \cs{indextrapage} or
% \cs{indextrarange} (see \fullref{Subsection}{subsec:user-redefinable-page-range}) will override this setting. If
% hyperlinks are required in this case, the user's redefinition should create them. \default{\val{true}}
%
%
%
% \subsection{Bookmarks}
%
% \DescribeOption{bookmarks}
% A boolean \val{true}/\val{false} that indicates whether the beginning of each group of index entries (usually terms
% beginning with a particular letter) should be bookmarked if the \cs{belowpdfbookmark} macro from the \pkg{hyperref}
% package is available. \default{\val{true}}
%
%
%
% \subsection{Headings}
%
% \DescribeOption{headings} A boolean \val{true}/\val{false} that indicates whether there should be a heading at the
% beginning of each group of index entries (usually terms beginning with a particular letter). This heading will be
% created by the user-redefinable macro \cs{indextramakegroupheading} (see
% \fullref{Subsection}{subsec:user-redefinable-heading}). \default{\val{true}}
%
%
%
% \section{Marks}
%
% \pkg{indextra} uses the keyword of each (top-level) index entry to insert a mark. The following macros make available
% the first and last marks on the current page. They are intended for use in a running header to show the range of
% entries on the current page.
%
% \begin{function}{\indextrafirstmark}
%   The first mark on the current page, with the macro \cs{indextramakemark} applied.
% \end{function}
%
% \begin{function}{\indextralastmark}
%   The last mark on the current page, with the macro \cs{indextramakemark} applied.
% \end{function}
%
%
%
% \section{User-redefinable macros}
%
% \subsection{Keywords}
%
% The following macros is used to typeset keywords of entries, sub-entries, and sub-sub-entries. It can be redefined by
% the user.
%
% \begin{function}{\indextrakeyword}
%   \begin{syntax}
%     \cs{indextrakeyword}\marg{level}\marg{keyword}
%   \end{syntax}
%   This macro is used to typeset a keyword for an entry of level \meta{level}. The default definition simply yields
%   \meta{keyword}. Users may wish to use a redefinition to apply styling or for other purposes. The result will already
%   have the code specified in \key{level \meta{level} style} applied.
% \end{function}
%
%
%
% \subsection{Page and range typesetting}
% \label{subsec:user-redefinable-page-range}
%
% The following two macros are actually used to typeset locations. They can be redefined by the user.
%
% \begin{function}{\indextrapage}
%   \begin{syntax}
%     \cs{indextrapage}\marg{encapsulation}\marg{page}
%   \end{syntax}
%   This command is used to typeset a reference to a single page. The default definition is effectively
%   \meta{encapsulation}\texttt{\{}\meta{page}\texttt{\}}, but using the configuration option \keyval{hyperindex}{true}
%   will create a hyperlink to the actual page. Any redefinition of this command will override the effect of
%   \keyval{hyperindex}{true}, so if the user still wishes the reference to be a hyperlink to the actual page, the new
%   definition must create the hyperlink.
% \end{function}
%
% \begin{function}{\indextrarange}
%   \begin{syntax}
%     \cs{indextrarange}\marg{encapsulation}\marg{start}\marg{end}
%   \end{syntax}
%   This command is used to typeset a reference to a range of pages. The default definition is effectively
%   \meta{encapsulation}\texttt{\{}\meta{page}\texttt{\}}\ttdashdash\meta{encapsulation}\texttt{\{}\meta{page}\texttt{\}},
%   but using the configuration option \keyval{hyperindex}{true} will create a hyperlink to the actual pages. Users may
%   wish to redefine \cs{indextrarange} to abbreviate ranges (so that, for instance, 1024--1025 is replaced by 1024--5).
%   Any redefinition of this command will override the effect of \keyval{hyperindex}{true}, so if the user still wishes
%   the references to be hyperlinks to the actual pages, the new definition must create the hyperlinks.
% \end{function}
%
%
%
% \subsection{Continuation text}
%
% \pkg{indextra} inserts continuation text for each entry, sub-entry, and sub-sub-entry that has been interrupted by a
% page or column break. The following macro creates the continuation text and can be redefined by the user.
%
% \begin{function}{\indextramakecontinuation}
%   \begin{syntax}
%     \cs{indextramakecontinuation}\marg{level}\marg{keyword}
%   \end{syntax}
%   This macro is used to generate a continuation text when the entry at \meta{level} with the supplied \meta{keyword}
%   contains a column break. The default definition simply yields \meta{keyword}\texttt{ (cont.)}. Users may wish to use
%   a redefinition to apply styling. The continuation text will already have the code specified in \key{level
%   \meta{level} style} applied.
% \end{function}
%
%
%
% \subsection{Marks}
%
% \begin{function}{\indextramakemark}
%   \begin{syntax}
%     \cs{indextramakemark}\marg{text}
%   \end{syntax}
%   This command is applied to any mark retrieved via either \cs{indextrafirstmark} or \cs{indextralastmark}. The
%   default definition simply yields \meta{text}. A redefinition could be used to abbreviate or otherwise process
%   \meta{text}.
% \end{function}
%
%
%
% \subsection{Headings}
% \label{subsec:user-redefinable-heading}
%
% If the option \keyval{headings}{true} is set, the following macro generates the group heading and can be
% redefined by the user.
%
% \begin{function}{\indextramakegroupheading}
%   \begin{syntax}
%     \cs{indextramakegroupheading}\marg{group}
%   \end{syntax}
%   This macro is used to generate a heading for \meta{group}. The default definition yields \cs{textbf}\marg{group}.
%   Users may wish to use a redefinition to apply a different style. Note that even if this command is redefined to
%   yield nothing, an extra vertical space will still be produced in the index where the heading would have been. To
%   disable headings and avoid this extra space, set \keyval{headings}{false} using \cs{indextrasetup}.
% \end{function}
%
%
%
% \section{Macros used in the generated \texorpdfstring{\file{.ind}}{.ind} file}
% \label{sec:ind-macros}
%
% For completeness, this section documents the commands and the enclosing environment used in the generated \file{.ind}
% file. None of these is intended to be used or redefined by the user.
%
% \begin{environment}{theindextra}
%   Environment containing the generated index.
% \end{environment}
%
% The commands below are only defined inside the \env{theindextra} environment.
%
% \begin{function}{\indextrastyleversion}
%   \begin{syntax}
%     \cs{indextrastyleversion}\marg{version}
%   \end{syntax}
%   Specify the \pkg{indextra} version of the style used to generate the \file{.ind} file. (This macro exists in case
%   future updates change the required format of the \file{.ind} file.)
% \end{function}
%
% \begin{function}{\indextraprocessortype}
%   \begin{syntax}
%     \cs{indextraprocessortype}\marg{type}
%   \end{syntax}
%   Specify the type of the processor used to generate the \file{.ind} file. (Different encapsulations of ranges, which
%   must be handled differently, are generated by \MakeIndex/\upmendex\ and by \xindy.) \marg{type} is either
%   \mcode{ist}, indicating that \MakeIndex\ or \upmendex\ was used, or \mcode{xdy}, indicating that \xindy\ was used.
% \end{function}
%
% \begin{function}{\indextraentry}
%   \begin{syntax}
%     \cs{indextraentry}\marg{level}\marg{keyword}\marg{crossrefs-and-locations}
%   \end{syntax}
%   This command typesets an index entry, sub-entry, or sub-sub-entry (if \meta{level} is respectively \mcode{0},
%   \mcode{1}, or \mcode{2}) with the given \meta{keyword}. The parameter \meta{crossrefs-and-locations} is a
%   comma-separated list of cross-references (using macros such as \cs{see} or \cs{seealso} as specified in the
%   \key{crossref macros} option) and locations (meaning pages or ranges).
% \end{function}
%
% \begin{function}{\indextraspace}
%   \begin{syntax}
%     \cs{indextraspace}
%   \end{syntax}
%   This command produces a space of one line in the index.
% \end{function}
%
% \begin{function}{\indextragroup}
%   \begin{syntax}
%     \cs{indextragroup}\marg{letter}
%   \end{syntax}
%   This command begins the new group \meta{letter}. Depending on configuration, it may produce a heading and/or a
%   bookmark.
% \end{function}
%
%
%
% \section{Usage notes and caveats}
% \label{sec:usage-notes}
%
% \subsection{Typesetting process}
%
% \pkg{indextra} works by tracking how much space is left in each column and intially typesetting each index entry into
% a buffer and measuring it. If there is enough space left in the column, the buffer contents are added to the output
% and the amount of space left is adjusted appropriately. Otherwise, enough of the buffer as will fit is split off and
% output, then a column break is called, the continuation text is added to the top of the remaining content in the
% buffer, and the process continues in a new column.
%
% This process relies on the fact that an index is a highly structured text with a restricted format. Any `exotic' index
% entries may break the process.
%
%
%
% \subsection{Limitations and incompatibilies}
% \label{subsec:limitations}
%
% \begin{itemize}
%
%     \item When choosing where to break a column, \pkg{indextra} only considers the current entry at the current level.
%           It does not, for example, automatically decide to insert a column break before a one-line top-level entry
%           that contains a sub-entry, even though this would be a more desirable result.
%
%     \item \pkg{indextra} cannot cope with \cs{verb} commands in index keywords. (But succeeding in generating such an
%           \file{.ind} via the usual \LaTeX\ indexing commands would be a challenge.)
%
%     \item \pkg{indextra} is incompatible with \pkg{imakeidx}, which uses the \pkg{multicols} package to set the index
%           instead of the native \LaTeX\ two-column mode. In particular, \LaTeX\ marks cannot be set from within
%           the \env{multicols} environment.
%
%     \item \pkg{indextra} should not be used with \pkg{repeatindex}, and in any case replaces its functionality.
%
%     \item \pkg{indextra} cannot be used if the usual indexing system is heavily customized. For example, the
%           \cls{l3doc} class uses its own specialized implementation of indexing and so \pkg{indextra} cannot be used
%           alongside it.
%
% \end{itemize}
%
%
%
% \subsection{Example of using marks}
% \label{subsec:example-marks}
%
% One could incorporate marks into the index running heads using by appending suitable code to the default value
% (\cs{begin}\mcode{\{theindex\}}) of the \key{before code} key:
% \iffalse
%<*example>
% \fi
\begin{lstlisting}
\indextrasetup{
  before code={%
    \begin{theindex}%
      \markboth
      {\MakeUppercase\indexname~%
        \noexpand\indextrafirstmark--\noexpand\indextralastmark}
      {\MakeUppercase\indexname~%
        \noexpand\indextrafirstmark--\noexpand\indextralastmark}%
  },
}
\end{lstlisting}
% \iffalse
%</example>
% \fi
% (The \cs{noexpand} macros ensure that \cs{indextrafirstmark} and \cs{indextralastmark} are expanded when
% the page is shipped out, not when \cs{markboth} is used.)
%
%
%
% \begin{thebibliography}{1}
%
% \bibitem{knuth_texbook}
% D.E.~Knuth.
% \newblock \emph{{T}he {\TeX book}}.
% \newblock Addison--Wesley, 2021.
% \newblock \emph{{C}omputers {\&} {T}ypesetting}, vol.~A.
%
% \end{thebibliography}
%
%
%
% \end{documentation}
%
%
%
% \begin{implementation}
%
%
%
% \section{Implementation}
%
%    \begin{macrocode}
%<*package>
%<@@=indextra>
%    \end{macrocode}
%
%
%
% \subsection{Initial set-up}
%
% Package identification/version information.
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}[2020-02-02]
\ProvidesExplPackage{indextra}{2025-10-26}{0.21.6}
  {Enhanced index typesetting}
%    \end{macrocode}
%
%
%
% \subsection{User configuration}
%
% Set up the key--value options and the variables in which the settings will be stored.
%
% \begin{variable}{
%   \l_@@_before_code_tl,
%   \l_@@_after_code_tl,
% }
%   Token list keys that store code to be executed before and after the index is typeset.
%    \begin{macrocode}
\keys_define:nn { indextra }
{
  before~code .tl_set:N = \l_@@_before_code_tl,
  after~code .tl_set:N = \l_@@_after_code_tl,
  before~code .initial:n = {\begin{theindex}},
  after~code .initial:n = {\end{theindex}},
}
%    \end{macrocode}
% \end{variable}
%
%
% \begin{variable}{
%   \l_@@_level_0_style_tl,
%   \l_@@_level_1_style_tl,
%   \l_@@_level_2_style_tl,
% }
%   Token list keys that store the style to be applied to entries at each level.
%    \begin{macrocode}
\keys_define:nn { indextra }
{
  level~0~style .tl_set:c = {l_@@_level_0_style_tl},
  level~1~style .tl_set:c = {l_@@_level_1_style_tl},
  level~2~style .tl_set:c = {l_@@_level_2_style_tl},
  level~0~style .initial:n = {\parindent=0em\hangindent=3.75em},
  level~1~style .initial:n = {\parindent=1.5em\hangindent=5.25em},
  level~2~style .initial:n = {\parindent=3em\hangindent=6.75em},
}
%    \end{macrocode}
% \end{variable}
%
%
% \begin{variable}{
%   \l_@@_separator_kw_cr_tl,
%   \l_@@_separator_kw_loc_tl,
%   \l_@@_separator_cr_cr_tl,
%   \l_@@_separator_cr_loc_tl,
%   \l_@@_separator_loc_loc_tl,
% }
%   Token list keys to store separators. Macros are abbreviated as follows \mcode{kw}: keyword; \mcode{cr}:
%   cross-reference; \mcode{loc}: location (page or range).
%    \begin{macrocode}
\keys_define:nn { indextra }
{
  separator~keyword~crossref .tl_set:N = \l_@@_separator_kw_cr_tl,
  separator~keyword~location .tl_set:N = \l_@@_separator_kw_loc_tl,
  separator~crossref~crossref .tl_set:N = \l_@@_separator_cr_cr_tl,
  separator~crossref~location .tl_set:N = \l_@@_separator_cr_loc_tl,
  separator~location~location .tl_set:N = \l_@@_separator_loc_loc_tl,
  separator~keyword~crossref .initial:n = {\break},
  separator~keyword~location .initial:n = {\space\space},
  separator~crossref~crossref .initial:n = {\break},
  separator~crossref~location .initial:n = {\break},
  separator~location~location .initial:n = {,~},
}
%    \end{macrocode}
% \end{variable}
%
%
% \begin{variable}{
%   \l_@@_crossref_macros_tl,
% }
%   Token list key to store macros that should count as cross-references.
%    \begin{macrocode}
\keys_define:nn { indextra }
{
  crossref~macros .tl_set:N = \l_@@_crossref_macros_tl,
  crossref~macros .initial:n = {\see\seealso},
}
%    \end{macrocode}
% \end{variable}
%
%
% \begin{variable}{
%   \l_@@_hyperindex_bool,
% }
%   Boolean indicating whether locations should be hyperlinked to pages, if \cs{hyperpage} is available and the user has
%   not redefined \cs{indextrapage} and \cs{indextrarange}.
%    \begin{macrocode}
\keys_define:nn { indextra }
{
  hyperindex .bool_set:N = \l_@@_hyperindex_bool,
  hyperindex .initial:n = {true},
}
%    \end{macrocode}
% \end{variable}
%
%
% \begin{variable}{
%   \l_@@_bookmarks_bool,
% }
%   Boolean indicating whether groups should be bookmarked, if \cs{belowpdfbookmark} is available.
%    \begin{macrocode}
\keys_define:nn { indextra }
{
  bookmarks .bool_set:N = \l_@@_bookmarks_bool,
  bookmarks .initial:n = {true},
}
%    \end{macrocode}
% \end{variable}
%
%
% \begin{variable}{
%   \l_@@_headings_bool,
% }
%   Boolean indicating whether group headings should appear.
%    \begin{macrocode}
\keys_define:nn { indextra }
{
  headings .bool_set:N = \l_@@_headings_bool,
  headings .initial:n = {true},
}
%    \end{macrocode}
% \end{variable}
%
%
% \begin{macro}{
%   \indextrasetup,
% }
%   User command to set key--value configuration.
%    \begin{macrocode}
\NewDocumentCommand{\indextrasetup}{ m }
{
  \keys_set:nn{ indextra }{ #1 }
}
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsection{Environment and commands for the \texorpdfstring{\file{.ind}}{.ind} file}
%
% Define the environment and commands used in the generated \file{.ind} file.
%
% \begin{macro}{
%   theindextra
% }
%   This environment is the analogue of the \env{theindex} environment defined by basic \LaTeX.
%    \begin{macrocode}
\NewDocumentEnvironment{theindextra}{}
  { \@@_main_begin: }
  { \@@_main_end: }
%
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \@@_main_begin:
% }
%   This macro (locally) sets various \TeX\ dimensions and defines the commands that are actually used in the index,
%   namely \cs{indextrastyleversion}, \cs{indextraprocessortype}, \cs{indextraspace}, \cs{indextragroup}, and
%   \cs{indextraentry}.%
%    \begin{macrocode}
\cs_new:Npn \@@_main_begin:
{
%    \end{macrocode}
%   Open a group and execute code for before the index has been typeset.
%    \begin{macrocode}
  \group_begin:
  \tl_use:N\l_@@_before_code_tl
%    \end{macrocode}
%   Since column breaks will be inserted manually, zero the penalties which influence column/page breaking.
%    \begin{macrocode}
  \int_zero:N\clubpenalty
  \int_zero:N\widowpenalty
%    \end{macrocode}
%   Set \cs{splittopskip}, \cs{topskip}, and \cs{parskip} to the needed values, and set
%   \cs{g_@@_remaining_space_dim}, which tracks the space left in the current column, to the initial value of
%   \cs{@colht}, which will be the height of the column after any initial two-column text has been typeset.
%    \begin{macrocode}
  \dim_gset:Nn\g_@@_remaining_space_dim{\@colht}
  \dim_set:Nn\splittopskip{.7\baselineskip}
  \dim_set:Nn\topskip{.7\baselineskip}
  \dim_set:Nn\parskip{0pt}
%    \end{macrocode}
%   Make available the appropriate macros.
%    \begin{macrocode}
  \cs_set_eq:NN\indextrastyleversion\@@_style_version:n
  \cs_set_eq:NN\indextraprocessortype\@@_processor_type:n
  \cs_set_eq:NN\indextraspace\@@_space:
  \cs_set_eq:NN\indextragroup\@@_group:n
  \cs_set_eq:NN\indextraentry\@@_entry:nnn
%    \end{macrocode}
%   If user settings require it, or \cs{belowpdfbookmark} is unavailable, disable group bookmarks and/or headings.
%    \begin{macrocode}
  \bool_if:nF
    { \l_@@_bookmarks_bool && \cs_if_exist_p:N\belowpdfbookmark }
    {
      \cs_set_eq:NN\@@_bookmark_stored_group:\prg_do_nothing:
    }
  \bool_if:NF\l_@@_headings_bool
    {
      \cs_set_eq:NN\@@_typeset_stored_group_heading:\prg_do_nothing:
    }
%    \end{macrocode}
%   Depending on user configuration, if \cs{hyperpage} is available and \cs{indextrapage} or \cs{indextrapage} have not
%   been redefined, then change them to create hyperlinks.
%    \begin{macrocode}
  \bool_if:nT{ \l_@@_hyperindex_bool && \cs_if_exist_p:N\hyperpage }
    {
      \cs_if_eq:NNT\indextrapage\@@_page_basic:nn
        {
          \cs_set_eq:NN\indextrapage\@@_page_hyperref:nn
        }
      \cs_if_eq:NNT\indextrarange\@@_range_basic:nnn
        {
          \cs_set_eq:NN\indextrarange\@@_range_hyperref:nnn
        }
    }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \@@_main_end:
% }
%   Execute code for after the index has been typeset and close the group opened in \cs{@@_main_begin:}.
%    \begin{macrocode}
\cs_new:Npn \@@_main_end:
{
  \tl_use:N\l_@@_after_code_tl
  \group_end:
}
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
\msg_new:nnn{ indextra }{ incompatible_version }
  { The~.ind~file~was~generated~using~a~style~from~a~different~version~of~indextra. }
%    \end{macrocode}%
%
% \begin{macro}{
%   \@@_style_version:n,
% }
%   Specify the version of \pkg{indextra}'s index processor style file (\file{.ist} or \file{.xdy}) that was used to
%   generate the \file{.ind} file. If is incompatible with version of \pkg{indextra} in use, an error will result.
%    \begin{macrocode}
\cs_new:Npn\@@_style_version:n #1
{
  \str_if_eq:nnF{#1}{0.1}
    {
      \msg_error:nn{ indextra }{ incompatible_version }
    }
}
%    \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}{
%   \@@_processor_type:n,
% }
%   Set the type of the processor used to generate the \file{.ind} file. \MakeIndex\ and \upmendex\ produce ranges of
%   the form \cs{encapsulation}\texttt{\{}\meta{start}\ttdashdash\meta{end}\texttt{\}}, while \xindy\ produces ranges of
%   the form \cs{encapsulation}\texttt{\{}\texttt{\}}\ttdashdash\cs{encapsulation}\texttt{\{}\texttt{\}}. These kinds of
%   ranges must be parsed differently, and while the kind of range could be detected when typesetting each location,
%   there are efficiency savings from using a dedicated parser in each case. \param{1} should be either \mcode{ist},
%   indicating that the processor was either \MakeIndex\ or \upmendex, or \mcode{xdy}, that the processor was \xindy.
%   This macro will be set equal to \cs{indextrasetprocessortype} inside the \env{theindextra} environment.
%    \begin{macrocode}
\cs_new:Npn\@@_processor_type:n #1
{
  \cs_set_eq:Nc\@@_typeset_aux_location:n
    { @@_typeset_#1_location:n }
}
%    \end{macrocode}
% \end{macro}
%
%
%
%
% \subsection{Column handling}
%
% \begin{variable}{
%   \g_@@_remaining_space_dim,
% }
%   Dimension variable to hold the amount of space left in the current column.
%    \begin{macrocode}
\dim_new:N\g_@@_remaining_space_dim
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{
%   \@@_new_column:
% }
%   Fill any remaining space in the current column, start a new column, and set \cs{g_@@_remaining_space_dim} to
%   \cs{@colht} (which is the amount of space available in the new column).
%    \begin{macrocode}
\cs_new:Npn\@@_new_column:
  {
    \vfill
    \newpage
    \dim_gset:Nn\g_@@_remaining_space_dim{\@colht}
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsection{Spaces}
%
% \begin{macro}{
%   \@@_space:,
% }
%   Create a space in the index, which will usually signify the end of one letter group and the beginning of another.
%   This takes up space in the column, so the space (a \cs{strut}) is typeset via the same mechanism as index entries,
%   namely \cs{@@_typeset_buffer:}.
%    \begin{macrocode}
\cs_new:Npn\@@_space:
  {
    \dim_compare:nNnT{\g_@@_remaining_space_dim}<{\baselineskip}{
      \@@_new_column:
    }
    \vbox_set:Nn\l_@@_buffer_box{\leavevmode\strut\par}
    \@@_typeset_buffer:
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsection{Groups}
%
% \begin{variable}{
%   \l_@@_group_tl
% }
%   Token-list variable to store the current group.
%    \begin{macrocode}
\tl_new:N\l_@@_group_tl
%    \end{macrocode}
% \end{variable}
%
%
% \begin{macro}{
%   \@@_group:,
% }
%   Begin a letter group in the index. The macro simply stores the group title; the bookmark and/or heading will be
%   created by the next index entry. This macro will be set equal to \cs{indextragroup} inside the \env{theindextra}
%   environment.
%    \begin{macrocode}
\cs_new:Npn\@@_group:n #1
  {
    \tl_set:Nn\l_@@_group_tl{#1}
  }
%    \end{macrocode}
% \end{macro}
%
%
% \begin{variable}{
%   \g_@@_group_bookmark_int
% }
%   Integer variable to index groups; used in the creation of bookmarks in \cs{@@_bookmark_stored_group:}.
%    \begin{macrocode}
\int_new:N\g_@@_group_bookmark_int
%    \end{macrocode}
% \end{variable}
%
%
% \begin{macro}{
%   \@@_bookmark_stored_group:
% }
%   If \cs{l_@@_group_tl} is non-empty, create a bookmark below the current level (i.e., using \cs{belowpdfbookmark}),
%   using \cs{l_@@_group_tl} for the bookmark text. The variable \cs{g_@@_group_bookmark_int} is incremented at each
%   call and used to create distinct bookmark names. (One cannot use the the content of \cs{l_@@_group_tl} for the
%   bookmark name, because (1) it might not be suitable as a name, and (2) there may be two indexes (e.g. names and
%   subjects) which contain the same groups.)
%    \begin{macrocode}
\cs_new:Npn\@@_bookmark_stored_group:
  {
    \tl_if_empty:NF\l_@@_group_tl
      {
        \int_gincr:N\g_@@_group_bookmark_int
        \belowpdfbookmark
          {\tl_use:N\l_@@_group_tl}
          {pdf:indextra:\int_value:w\g_@@_group_bookmark_int}
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{
%   \@@_typeset_stored_group_heading:
% }
%   If \cs{l_@@_group_tl} is non-empty, typeset its contents as a heading for the group.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_stored_group_heading:
  {
    \tl_if_empty:NF\l_@@_group_tl
      {
        \group_begin:
        \noindent\strut
        \indextramakegroupheading{\tl_use:N\l_@@_group_tl}
        \strut\par
        \group_end:
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{
%   \indextramakegroupheading
% }
%   Typeset a heading for the letter group passed as \param{1}.
%    \begin{macrocode}
\cs_new:Npn\indextramakegroupheading #1
  {
    \textbf{#1}
  }
%    \end{macrocode}
% \end{macro}
%
%
%
%
%
% \subsection{Entries}
%
% \begin{variable}{
%   \l_@@_level_int
% }
%   Integer variable to hold the level of the current entry.
%    \begin{macrocode}
\int_new:N\l_@@_level_int
%    \end{macrocode}
% \end{variable}
%
%
% \begin{variable}{
%   \l_@@_saved_keyword_0,
%   \l_@@_saved_keyword_1,
%   \l_@@_saved_keyword_2,
% }
%   Token list variables to save keywords at each level.
%    \begin{macrocode}
\tl_new:c{l_@@_saved_keyword_0}
\tl_new:c{l_@@_saved_keyword_1}
\tl_new:c{l_@@_saved_keyword_2}
%    \end{macrocode}
% \end{variable}
%
%
% \begin{variable}{
%   \l_@@_crossref_seq,
%   \l_@@_location_seq,
% }
%   Comma-separated list variables to hold the cross-references and locations of the current entry. These will be
%   populated via a call to \cs{@@_filter_crossref_location_seqs:n}
%    \begin{macrocode}
\seq_new:N\l_@@_crossref_seq
\seq_new:N\l_@@_location_seq
%    \end{macrocode}
% \end{variable}
%
%
% \begin{macro}{
%   \@@_entry:nnn,
% }
%   Typeset an index entry. The three parameters are the level (\param{1}), the keyword (\param{2}), and the rest of the
%   entry (\param{3}), which should be a comma-separated list of cross-references and/or locations.
%    \begin{macrocode}
\cs_new:Npn\@@_entry:nnn #1#2#3
  {
%    \end{macrocode}
%   First save the level and the keyword, and sort the rest of the entry into the cross-reference and location seqs.
%    \begin{macrocode}
    \int_set:Nn\l_@@_level_int{#1}
    \tl_set:cn{l_@@_saved_keyword_#1}{#2}
    \@@_filter_crossref_location_seqs:n{#3}
%    \end{macrocode}
%   Typeset the entire entry (prefixed by any stored group heading) into \cs{l_@@_buffer_box}. The bookmarking command
%   is also executed here, since it is the contents of this box that will ultimately be shipped out.
%    \begin{macrocode}
    \vbox_set:Nn\l_@@_buffer_box{
      \@@_bookmark_stored_group:
      \@@_typeset_stored_group_heading:
      \group_begin:
      \@@_set_style:n{#1}
      \leavevmode
      \strut
      \indextrakeyword{#1}{#2}
      \@@_typeset_between:
      \@@_typeset_locations:
      \strut
      \par
      \goodbreak
      \group_end:
    }
%    \end{macrocode}
%   Now typeset some material into \cs{l_tmpa_box} and \cs{l_tmpb_box}, the dimensions of which will be used in
%   computing the minimum acceptable portion of the entry to typeset into the current column. First, typeset the part of
%   the entry where a column break is unacceptable (prefixed by any stored group heading) into \cs{l_tmpa_box}.
%    \begin{macrocode}
    \vbox_set:Nn\l_tmpa_box{
      \@@_typeset_stored_group_heading:
      \group_begin:
      \@@_set_style:n{#1}
      \leavevmode
      \strut
      \indextrakeyword{#1}{#2}
      \@@_typeset_between:
      \@@_typeset_first_location:
      \strut
      \par
      \group_end:
    }
%    \end{macrocode}
%   Typeset any stored group heading into \cs{l_tmpb_box}.
%    \begin{macrocode}
    \vbox_set:Nn\l_tmpb_box{
      \@@_typeset_stored_group_heading:
    }
%    \end{macrocode}
%   Measuring \cs{l_tmpa_box}, \cs{l_tmpb_box}, and \cs{l_@@_buffer_box}, check whether there is enough space in the
%   current column to typeset any stored heading and the minimum acceptable portion of the entry. Basically, a split
%   should not occur within the part typeset into \cs{l_tmpa_box}, and if the entry as a whole is longer than one line,
%   then at least two lines should be typeset (because if only one line can fit into the current column, there is
%   probably no overall saving of space by putting it in the current column with a continuation in the next column).
%   Note that if there is no stored heading, the height and depth of \cs{l_tmpb_box} are both \qty{0}{\point}, so no
%   conditional is needed.
%    \begin{macrocode}
    \dim_compare:nNnT
      {\g_@@_remaining_space_dim}
      <
      {
        \dim_min:nn{
          \dim_max:nn{
            \box_ht:N\l_tmpa_box
            +\box_dp:N\l_tmpa_box
          }{
            \box_ht:N\l_tmpb_box
            +\box_dp:N\l_tmpb_box
            +2\baselineskip
          }
        }{
          \box_ht:N\l_@@_buffer_box
          +\box_dp:N\l_@@_buffer_box
        }
      }
      {
%    \end{macrocode}
%   \textit{Case: not enough space in the current column.} Start a new column and, unless this is a top-level
%   entry, insert the appropriate continuation into the \cs{l_@@_buffer_box}.
%    \begin{macrocode}
        \@@_new_column:
        \int_compare:nNnT{#1}>{0}{
          \@@_vbox_prepend:Nn\l_@@_buffer_box{
            \@@_make_continuation:n{\l_@@_level_int-1}
          }
        }
      }
%    \end{macrocode}
%   \cs{l_@@_buffer_box} now contains the material that should be typeset onto the page, and there is enough space in
%   the current column to typeset a minimal acceptable portion of it. Typesetting is handled by \cs{@@_set_buffer:},
%   which may use \tn{vsplit}, which produces a warning `\texttt{Underfull \tn{vbox} (badness 10000)}' with even a
%   \qty{1}{\spoint} mismatch between available breakpoints and desired height. For efficiency, \cs{@@_set_buffer:} uses
%   an approximation to the desired height, so set \tn{vbadness} to 10000 to suppress these warnings.
%    \begin{macrocode}
    \group_begin:
    \int_set:Nn\vbadness{10000}
    \@@_typeset_buffer:
    \group_end:
%    \end{macrocode}
%   Finally, clear any stored group.
%    \begin{macrocode}
    \tl_clear:N\l_@@_group_tl
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Filtering cross-references and locations}
%
% In each entry, cross-references and locations may be mixed up and need to be filtered into separate lists.
%
% \begin{macro}{
%   \@@_filter_crossref_location_seqs:n
% }
%   Use the supplied list of cross-references and location (\param{1}), which is a clist, to populate
%   \cs{l_@@_crossref_seq} and \cs{l_@@_location_seq}. The list of macros that are the first tokens in
%   cross-references is contained in \cs{l_@@_crossref_macros_tl}. All that is required is to check whether the head
%   token in each item in the \param{1} is in \cs{l_@@_crossref_macros_tl}. and assign that item to the correct seq.
%    \begin{macrocode}
\cs_new:Npn\@@_filter_crossref_location_seqs:n #1
  {
    \seq_clear:N\l_@@_crossref_seq
    \seq_clear:N\l_@@_location_seq

    \clist_map_inline:nn{#1}
      {
        \tl_if_in:NoTF
          \l_@@_crossref_macros_tl{ \tl_head:w ##1 {} \q_stop }
          {
            \seq_put_right:Nn\l_@@_crossref_seq{##1}
          }
          {
            \seq_put_right:Nn\l_@@_location_seq{##1}
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Style}
%
% \begin{macro}{
%   \@@_set_style:n,
% }
%   Set up the style for an index entry at the given level.
%    \begin{macrocode}
\cs_new:Npn \@@_set_style:n #1
  {
    \tl_use:c{l_@@_level_#1_style_tl}
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Typesetting keywords}
%
% \begin{macro}{
%   \indextrakeyword
% }
%   Take a level (\param{1}) and a keyword (\param{2}) and return a (possibly styled) keyword.
%    \begin{macrocode}
\cs_new:Npn\indextrakeyword #1#2
  {
    #2
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Typesetting cross-references}
%
% \begin{macro}{
%   \@@_typeset_between:
% }
%   Typeset everything between the keyword and the locations. If there are no cross-references, this means typesetting
%   just the keyword--location separator if the list of locations is non-empty (it is possible that there are no
%   locations; practically, this would happen when all the locations are in sub-entries); otherwise, it means
%   typesetting the cross-references with the relevant separators before, after, and between.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_between:
  {
    \seq_if_empty:NTF\l_@@_crossref_seq
      {
        \seq_if_empty:NF\l_@@_location_seq
          { \tl_use:N\l_@@_separator_kw_loc_tl }
      }
      {
        \tl_use:N\l_@@_separator_kw_cr_tl
        \seq_use:Nn
          \l_@@_crossref_seq
          { \tl_use:N\l_@@_separator_cr_cr_tl }
        \seq_if_empty:NF\l_@@_location_seq
          { \tl_use:N\l_@@_separator_cr_loc_tl }
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsubsection{Typesetting locations}
%
% \begin{variable}{
%   \l_@@_first_item_bool,
% }
%   Boolean variable to track whether the current location is the first one in the entry, so that a separator can be
%   inserted before every non-first location.
%    \begin{macrocode}
\bool_new:N\l_@@_first_item_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{
%   \@@_typeset_locations:
% }
%   Typeset locations stored in \cs{l_@@_locations_seq}. Using \cs{seq_use:Nn} is not enough, because each entry must be
%   processed separately. Thus separators must be inserted `manually'.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_locations:
  {
    \bool_set_true:N\l_@@_first_item_bool
    \seq_map_inline:Nn\l_@@_location_seq
      {
        \bool_if:nF{\l_@@_first_item_bool}
          { \tl_use:N\l_@@_separator_loc_loc_tl }
        \@@_typeset_aux_location:n{##1}
        \bool_set_false:N\l_@@_first_item_bool
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \@@_typeset_first location:
% }
%   Typeset the first location stored in \cs{l_@@_locations_seq}.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_first_location:
  {
    \exp_args:Ne\@@_typeset_aux_location:n
      {\seq_item:Nn\l_@@_location_seq{1}}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \@@_is_nonrange_p:w
% }
%   To be called in the form \cs{@@_is_nonrange_p:w}\meta{parameter}\ttdashdash\cs{q_stop}; uses \TeX\ parsing to check
%   whether \meta{parameter} does not contain a range marker \ttdashdash.
%    \begin{macrocode}
\cs_new:Npn\@@_is_nonrange_p:w #1--#2\q_stop
  {
    \tl_if_empty_p:n{#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \cs_new:Npn\@@_typeset_ist_location:n
% }
%   Typeset a \MakeIndex- or \upmendex-style location. The location might be a
%   \begin{itemize}
%     \item a single page;
%     \item an encapsulated single page \cs{encapsulation}\mcode{\{}\meta{page}\mcode{\}};
%     \item a range; or
%     \item an encapsulated range \cs{encapsulation}\mcode{\{}\meta{start}\ttdashdash\meta{end}\mcode{\}}.
%   \end{itemize}
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_ist_location:n #1
  {
%    \end{macrocode}
%   Check for an encapsulation (a leading control sequence, comparing catcode with \cs{prg_do_nothing:} simply as a
%   convenience). Either call \cs{@@_typeset_ist_encap_page_or_range:Nn} with this encapsulation or with a `do-nothing'
%   encapsulation and the braced parameter.
%    \begin{macrocode}
    \tl_if_head_eq_catcode:nNTF{#1}\prg_do_nothing:
      {
        \@@_typeset_ist_encap_page_or_range:Nn #1
      }
      {
        \@@_typeset_ist_encap_page_or_range:Nn \prg_do_nothing:{#1}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \@@_typeset_ist_encap_page_or_range:Nn
% }
%   Take an encapsulation and a parameter and typeset the parameter as a page or range, as appropriate.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_ist_encap_page_or_range:Nn #1#2
  {
    \bool_if:nTF{\@@_is_nonrange_p:w #2--\q_stop}
      {
%    \end{macrocode}
%   \textit{Case: not a range.} Parameter \param{2} a single page.
%    \begin{macrocode}
        \indextrapage{#1}{#2}
      }
      {
%    \end{macrocode}
%   \textit{Case: range.} Parameter \param{2} is a range. Call \cs{@@_typeset_ist_encap_range:w} to parse and
%   typeset it.
%    \begin{macrocode}
        \@@_typeset_ist_encap_range:w#1\q_mark #2\q_stop
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \@@_typeset_ist_encap_range:w
% }
%   This is effectively just a helper macro so that \TeX\ parsing can be used to convert the parameters into the form
%   required by \cs{indextrarange}.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_ist_encap_range:w #1\q_mark #2--#3\q_stop
  {
    \indextrarange{#1}{#2}{#3}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \cs_new:Npn\@@_typeset_xdy_location:n
% }
%   Typeset a \xindy-style location. The location might be
%   \begin{itemize}
%     \item a single page;
%     \item an encapsulated single page \cs{encapsulation}\mcode{\{}\meta{page}\mcode{\}};
%     \item a range; or
%     \item an encapsulated range \cs{encapsulation}\mcode{\{}\meta{start}\mcode{\}}\ttdashdash\cs{encapsulation}\mcode{\{}\meta{end}\mcode{\}}.
%   \end{itemize}
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_xdy_location:n #1
  {
    \bool_if:nTF{\@@_is_nonrange_p:w #1--\q_stop}
      {
%    \end{macrocode}
%   \textit{Case: not a range.} Check for an encapsulation (a leading control sequence, comparing catcode with
%   \cs{prg_do_nothing:} simply as a convenience). Either call \cs{@@_typeset_ist_encap_page_or_range:Nn} with this
%   encapsulation or with a `do-nothing' encapsulation and the braced parameter.
%    \begin{macrocode}
        \tl_if_head_eq_catcode:nNTF{#1}\prg_do_nothing:
          {
            \indextrapage #1
          }
          {
            \indextrapage \prg_do_nothing:{#1}
          }
      }
      {
%    \end{macrocode}
%   \textit{Case: range.}
%    \begin{macrocode}
        \@@_typeset_xdy_range:w #1\q_stop
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \@@_typeset_xdy_range:w
% }
%   To be called in the form \cs{@@_typeset_xdy_range:w}\meta{parameter}\cs{q_stop}, so that \param{1} and \param{2}
%   will be the start and end of the range, possibly with encapsulations. Check \param{1} for a leading control
%   sequence. If it exists, assume the same holds for \param{2}, and the parameters are of the form
%   \cs{encapsulation}\texttt{\{}\meta{start}\texttt{\}} and \cs{encapsulation}\texttt{\{}\meta{end}\texttt{\}} and can
%   be passed to \cs{@@_typeset_xdy_encap_range:w}. Otherwise, each parameter is a page, so call \cs{indextrarange} with
%   `do-nothing' encapsulations and braced parameters.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_xdy_range:w #1--#2\q_stop
  {
    \tl_if_head_eq_catcode:nNTF{#1}\prg_do_nothing:
      {
        \@@_typeset_xdy_encap_range:NnNn #1#2
      }
      {
        \indextrarange\prg_do_nothing:{#1}{#2}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \@@_typeset_xdy_encap_range:NnNn
% }
%   This is effectively just a helper macro so that \TeX\ parsing can be used to convert the parameters into the form
%   required by \cs{indextrarange}.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_xdy_encap_range:NnNn #1#2#3#4
  {
    \indextrarange{#1}{#2}{#4}
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}{
%   \@@_page_basic:nn,
%   \@@_range_basic:nnn,
%   \@@_page_hyperref:nn,
%   \@@_range_hyperref:nnn,
% }
%   Default macros for typesetting a page and a range, depending on whether \pkg{hyperref} is loaded.
%    \begin{macrocode}
\cs_new:Npn\@@_page_basic:nn #1#2
  {
    #1{#2}
  }
\cs_new:Npn\@@_range_basic:nnn #1#2#3
  {
    #1{#2}--#1{#3}
  }
\cs_new:Npn\@@_page_hyperref:nn #1#2
  {
    #1{\hyperpage{#2}}
  }
\cs_new:Npn\@@_range_hyperref:nnn #1#2#3
  {
    #1{\hyperpage{#2}}--#1{\hyperpage{#3}}
  }
%    \end{macrocode}
% \end{macro}
%
% Set \cs{indextrapage} and \cs{indextrarange} to the `basic' versions of the above macros. They may be set to the
% `hyperref' versions at the start of the \env{theindextra} environment depending on user settings.
%    \begin{macrocode}
\cs_set_eq:NN\indextrapage\@@_page_basic:nn
\cs_set_eq:NN\indextrarange\@@_range_basic:nnn
%    \end{macrocode}
%
%
% \subsection{Continuations}
%
% \begin{macro}{
%   \@@_make_continuation:n
% }
%   Return a continuation text for the level specified in \param{1}, indicating that this (and higher) levels are
%   continued from the previous column. The continuation text is created using the stored keywords with
%   \cs{indextramakecontinuation} applied to each.
%    \begin{macrocode}
\cs_new:Npn\@@_make_continuation:n #1
  {
    \int_step_inline:nnn{0}{#1}{
      \group_begin:
      \@@_set_style:n{##1}
      \leavevmode\strut
      \indextramakecontinuation{##1}{\tl_use:c{l_@@_saved_keyword_##1}}
      \strut\par
      \group_end:
    }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \indextramakecontinuation
% }
%   Take a level (\param{1}) and a keyword (\param{2}) and return a continuation text.
%    \begin{macrocode}
\cs_new:Npn\indextramakecontinuation #1#2
  {
    #2~(cont.)
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsection{Marks}
%
% Define a new mark class for use by \pkg{indextra}.
%    \begin{macrocode}
\mark_new_class:n{indextra}
%    \end{macrocode}
%
% \begin{macro}{
%   \@@_mark_insert:
% }
%   Insert the top-level saved keyword as a mark.
%    \begin{macrocode}
\cs_new:Npn\@@_mark_insert:
  {
    \mark_insert:nn{indextra}{\tl_use:c{l_@@_saved_keyword_0}}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \indextramakemark
% }
%   Macro to process an \pkg{indextra} mark for use. Any mark retrieved by \cs{indextrafirstmark} or
%   \cs{indextralastmark} will have \cs{indextramakemark} applied.
%    \begin{macrocode}
\cs_new:Npn\indextramakemark #1
  {
    #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{
%   \indextrafirstmark,
%   \indextralastmark,
% }
%   Retrieve the first and last \pkg{indextra} marks on the page, where `first' and `last' have their standard \LaTeX\
%   meanings. The marks will be processed by \cs{indextramakemark}.
%    \begin{macrocode}
\cs_new:Npn\indextrafirstmark
  {
    \indextramakemark{ \mark_use_first:nn{page}{indextra} }
  }
\cs_new:Npn\indextralastmark
  {
    \indextramakemark{ \mark_use_last:nn{page}{indextra} }
  }
%    \end{macrocode}
% \end{macro}
%
%
%
% \subsection{Buffer}
%
% \begin{variable}{
%   \l_@@_buffer_box,
% }
%   Box variable to hold remaining material to be typeset for the current index entry, sub-entry, or sub-sub-entry.
%    \begin{macrocode}
\box_new:N\l_@@_buffer_box
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{
%   \@@_typeset_buffer:
% }
%   Output material that has been typeset into \cs{l_@@_buffer_box} and update \cs{g_@@_remaining_space_dim} as
%   necessary. If necessary, split off as much material as will fit in the current column from \cs{l_@@_buffer_box},
%   typeset it, add continuation text to the top of \cs{l_@@_buffer_box}, then recursively call this macro. The
%   recursion ends when all material in \cs{l_@@_buffer_box} has been output.
%    \begin{macrocode}
\cs_new:Npn\@@_typeset_buffer:
  {
%    \end{macrocode}
%   Insert a mark. This is either on the first call, or a recursive call, in which case the current position is at the
%   top of the column. A mark should be inserted in any case.
%    \begin{macrocode}
    \@@_mark_insert:
%    \end{macrocode}
%   Check whether it is necessary to split off some material for this column and start a new one. There is no need to
%   check about the minimum acceptable split: if necessary, a new column has already been started in \cs{@@_entry:nnn}.
%    \begin{macrocode}
    \dim_compare:nNnT
    {\g_@@_remaining_space_dim}
    <
    {\box_ht:N\l_@@_buffer_box+\box_dp:N\l_@@_buffer_box}
      {
%    \end{macrocode}
%   \textit{Case: A split is necessary.} Compute an approximation (definitely too large, but acceptably so) to the
%   amount to be split off, then split and rebox.
%    \begin{macrocode}
        \dim_set:Nn
          \l_tmpa_dim{\g_@@_remaining_space_dim-.3\baselineskip}
        \vbox_set_split_to_ht:NNn
          \l_tmpa_box\l_@@_buffer_box{\l_tmpa_dim}
        \vbox_set:Nn
          \l_tmpa_box{\vbox_unpack_drop:N\l_tmpa_box}
%    \end{macrocode}
%   Update \cs{g_@@_remaining_space_dim} and output the split material.
%    \begin{macrocode}
        \dim_gset:Nn\g_@@_remaining_space_dim
          {
            \g_@@_remaining_space_dim
            -\box_ht:N\l_tmpa_box
            -\box_dp:N\l_tmpa_box
          }
        \vbox_unpack_drop:N\l_tmpa_box
%    \end{macrocode}
%   Add a continuation text to the top of \cs{l_@@_buffer_box}, start a new column, and recurse.
%    \begin{macrocode}
        \@@_vbox_prepend:Nn
          \l_@@_buffer_box
          {\@@_make_continuation:n{\l_@@_level_int}}
        \@@_new_column:
        \@@_typeset_buffer:
      }
      {
%    \end{macrocode}
%   \textit{Case: No split is necessary.} Update \cs{g_@@_remaining_space_dim} and output the buffer contents.
%    \begin{macrocode}
        \dim_gset:Nn\g_@@_remaining_space_dim
          {
            \g_@@_remaining_space_dim
            -\box_ht:N\l_@@_buffer_box
            -\box_dp:N\l_@@_buffer_box
          }
        \vbox_unpack_drop:N\l_@@_buffer_box
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{
%   \@@_vbox_prepend
% }
%   Takes a vertical box variable as \param{1} and typeset the material in \param{2} into it above the original content.
%    \begin{macrocode}
\cs_new:Npn\@@_vbox_prepend:Nn #1#2
{
  \vbox_set:Nn #1{
    \vbox{#2}
    \vbox_unpack_drop:N #1
  }
}
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
%
%
% \subsection{Style files}
%
% \subsubsection{\texorpdfstring{\file{.ist}}{ist} -- \MakeIndex, \upmendex}
%
%    \begin{macrocode}
%<*ist>
%    \end{macrocode}
%
%    \begin{macrocode}
page_precedence "ArRn"

preamble        "\\begin{theindextra}\n  \\indextrastyleversion{0.1}\n  \\indextraprocessortype{ist}\n"
postamble       "\n\n\n\\end{theindextra}\n"

group_skip      "\n\n\n  \\indextraspace"

heading_prefix  "\n\n\n  \\indextragroup{"
heading_suffix  "}\n"
headings_flag   1

item_0          "\n  \\indextraentry{0}{"
item_1          "\n    \\indextraentry{1}{"
item_01          "\n    \\indextraentry{1}{"
item_x1          "}{}\n    \\indextraentry{1}{"
item_2          "\n      \\indextraentry{2}{"
item_12          "\n    \\indextraentry{2}{"
item_x2          "}{}\n    \\indextraentry{2}{"

delim_0         "}{"
delim_1         "}{"
delim_2         "}{"
delim_t         "}"

encap_prefix    "\\"
encap_infix     "{"
encap_suffix    "}"
%    \end{macrocode}
%
%    \begin{macrocode}
%</ist>
%    \end{macrocode}
%
%
%
% \subsubsection{\texorpdfstring{\file{.xdy}}{xdy} -- \xindy}
%
%    \begin{macrocode}
%<*xdy>
%    \end{macrocode}
%
%    \begin{macrocode}
(markup-index :open  "\begin{theindextra}~n  \indextrastyleversion{0.1}~n  \indextraprocessortype{xdy}~n"
              :close "~n~n\end{theindextra}~n"
              :tree)

(markup-letter-group-list :sep "~n  \indextraspace~n")
(markup-letter-group :open-head "~n  % "
                     :close-head "~n"
                     :group "default")
(markup-letter-group :open-head "~n  \indextragroup{"
                     :close-head "}~n~n")

(markup-indexentry :open "  \indextraentry{0}{"
                   :close "}%~n"
                   :depth 0)
(markup-indexentry :open "}%~n    \indextraentry{1}{"
                   :close ""
                   :depth 1)
(markup-indexentry :open "}%~n      \indextraentry{2}{"
                   :close ""
                   :depth 2)

(markup-keyword-list :open "" :close "}{" :sep ";")

(markup-locclass-list :open "" :sep ", " :close "")
(markup-locref-list :open ""  :sep ", " :close "")

(markup-range :sep "--")

(markup-crossref-list :class "see"
                      :open "\see{"
                      :sep "}{}, \see{"
                      :close "}{}")
(markup-crossref-list :class "seealso"
                      :open "\seealso{"
                      :sep "}{}, \seealso{"
                      :close "}{}")
%    \end{macrocode}
%
%    \begin{macrocode}
%</xdy>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
%
%
%
% \iffalse
%<*metadriver>
\input{indextra.dtx}
%</metadriver>
%
%<*demo>
\documentclass{article}

\usepackage[
paperwidth=105mm,
paperheight=100mm,
inner=5mm,
width=90mm,
height=90mm,
top=5mm,
nohead,
nofoot,
columnsep=5mm,
twocolumn,
]{geometry}

\usepackage{xcolor}
\definecolor{bg}{gray}{0.9}
\pagecolor{bg}


\parindent=1em

\usepackage{indextra}

\indextrasetup{
  before code={},
  after code={},
}


\newcommand\see[1]{\textit{see}~`#1'}
\newcommand\seealso[1]{\textit{see also}~`#1'}


\raggedright
\pagestyle{empty}

\begin{document}

\begin{theindextra}
  \indextrastyleversion{0.1}
  \indextraprocessortype{ist}

  \indextraentry{0}{neutron}{855, 862, 885--886}%
  \indextraentry{0}{\textit{New Astronomy} (Kepler)}{\see{\textit{Astronomia Nova}}{}}%
  \indextraentry{0}{Newtonian physics}{298, 376, 390, 589, 813, 830, 834, 842, 891, 894--895}%
  \indextraentry{0}{\textit{Nicomachean Ethics} (Aristotle)}{85}%
  \indextraentry{0}{Nim}{11}%
  \indextraentry{0}{nine-point circle}{584, 641, 662--663}%
  \indextraentry{0}{Nobel Prize}{}%
    \indextraentry{1}{chemistry}{495, 566, 815, 865, 937}%
    \indextraentry{1}{literature}{485}%
    \indextraentry{1}{peace}{865}%
    \indextraentry{1}{physics}{502, 598, 727, 834, 836, 838, 851, 853--854, 856, 865, 867, 877, 893, 903, 905, 918, 921}%
  \indextraentry{0}{nobility}{188, 240, 250, 464}%
  \indextraentry{0}{non-aesthetic property}{700, 715}%
  \indextraentry{0}{non-associative algebra}{848}%
  \indextraentry{0}{non-commutativity}{643}%
  \indextraentry{0}{non-constructive proof}{787}%
  \indextraentry{0}{non-determinism}{595}%
  \indextraentry{0}{non-euclidean geometry}{380, 401, 404--408, 436, 628, 643, 880, 896}%
  \indextraentry{0}{non-linearity}{882}%
  \indextraentry{0}{non-measurable set}{672}%
  \indextraentry{0}{non-sensory property}{695}%
  \indextraentry{0}{non-visual thinking}{633}%
  \indextraentry{0}{nonagon}{220}%

\end{theindextra}

\end{document}
% </demo>
% \fi
