summaryrefslogtreecommitdiff
path: root/macros/luatex/generic
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2023-09-11 03:03:32 +0000
committerNorbert Preining <norbert@preining.info>2023-09-11 03:03:32 +0000
commitf2abd8392e16d9b51a73c3c09b322ddde8b9be9e (patch)
treea87495ceeba6fec586d59fe9ce7923a381e221c3 /macros/luatex/generic
parent5aeceaafdf41d8bd02ef1521380f21cbb163bd69 (diff)
CTAN sync 202309110303
Diffstat (limited to 'macros/luatex/generic')
-rw-r--r--macros/luatex/generic/nodetree/README.md6
-rw-r--r--macros/luatex/generic/nodetree/nodetree-doc.tex1444
-rw-r--r--macros/luatex/generic/nodetree/nodetree.dtx83
-rw-r--r--macros/luatex/generic/nodetree/nodetree.ins4
-rw-r--r--macros/luatex/generic/nodetree/nodetree.lua1824
-rw-r--r--macros/luatex/generic/nodetree/nodetree.pdfbin279353 -> 290864 bytes
6 files changed, 2913 insertions, 448 deletions
diff --git a/macros/luatex/generic/nodetree/README.md b/macros/luatex/generic/nodetree/README.md
index f9a9c45b31..f9fdb53fff 100644
--- a/macros/luatex/generic/nodetree/README.md
+++ b/macros/luatex/generic/nodetree/README.md
@@ -18,7 +18,7 @@ terminal to generate the document.
# License
-Copyright (C) 2016-2022 by Josef Friedrich <josef@friedrich.rocks>
+Copyright (C) 2016-2023 by Josef Friedrich <josef@friedrich.rocks>
------------------------------------------------------------------------
This work may be distributed and/or modified under the conditions of
the LaTeX Project Public License, either version 1.3 of this license
@@ -150,8 +150,8 @@ if not modules then modules = { } end modules ['nodetree'] = {
### Update the copyright year:
```
-sed -i 's/(C) 2016-2022/(C) 2016-2021/g' nodetree.ins
-sed -i 's/(C) 2016-2022/(C) 2016-2021/g' nodetree.dtx
+sed -i 's/(C) 2016-2023/(C) 2016-2021/g' nodetree.ins
+sed -i 's/(C) 2016-2023/(C) 2016-2021/g' nodetree.dtx
```
### Command line tasks:
diff --git a/macros/luatex/generic/nodetree/nodetree-doc.tex b/macros/luatex/generic/nodetree/nodetree-doc.tex
new file mode 100644
index 0000000000..d9531ad9f2
--- /dev/null
+++ b/macros/luatex/generic/nodetree/nodetree-doc.tex
@@ -0,0 +1,1444 @@
+%!TEX program = lualatex
+\documentclass{ltxdoc}
+\usepackage{paralist,fontspec,microtype}
+\usepackage[
+ colorlinks=true,
+ linkcolor=red,
+ filecolor=red,
+ urlcolor=red,
+]{hyperref}
+\usepackage{nodetree-embed}
+\mdfsetup{
+ innerleftmargin=0.2em,
+ innerrightmargin=0.2em,
+}
+\EnableCrossrefs
+\CodelineIndex
+\RecordChanges
+
+% Improve ltxdoc's `|...|` feature by allowing breaks.
+\usepackage{fvextra}
+\fvset{breaklines=true,breakbefore=.-_}
+\AtBeginDocument{\DefineShortVerb{\|}}
+
+% We use 'pmboxdraw' to draw box elements in minted environments.
+
+% https://tex.stackexchange.com/questions/281368/print-box-drawing-characters-with-pdflatex/355403#355403
+\usepackage{pmboxdraw}
+\begingroup
+ \def\DeclareUnicodeCharacter#1{%
+ \begingroup
+ \lccode`\~="#1\relax
+ \lowercase{\endgroup
+ \global\catcode`~=\active
+ \gdef~%
+ }%
+ }%
+ \input{pmboxdrawenc.dfu}%
+\endgroup
+
+% https://tex.stackexchange.com/questions/108127/block-element-characters-pmboxdraw-are-shown-too-wide-in-verbatim-and-verbatim
+\pmboxdrawsetup{
+ Block/box={\texttt{0}},
+}
+
+\usepackage{minted}
+\usemintedstyle{colorful}
+\BeforeBeginEnvironment{minted}{\begin{mdframed}[backgroundcolor=gray!3]}
+\AfterEndEnvironment{minted}{\end{mdframed}}
+\setminted{
+ breaklines=true,
+ fontsize=\footnotesize,
+}
+\setmintedinline{
+ fontsize=auto
+}
+
+\def\TmpLuaCodeInline#1{\texttt{\scantokens{\catcode`\_=12\relax#1}}}
+
+\def\TmpSecRef#1{(\rightarrow\ \ref{#1})}
+
+\def\TmpPageSecRef#1{
+ Page
+ \pageref{#1},
+ Section
+ \ref{#1}
+}
+
+\newcommand{\TmpExample}[2]{
+\begin{NodetreeEmbedView}[fontsize=#2]
+\input{examples/#1.nttex}
+\end{NodetreeEmbedView}
+}
+
+\newcommand{\TmpVerbExample}[2]{
+\VerbatimInput[firstline=4]{examples/#1.tex}
+\TmpExample{#1}{#2}
+}
+
+\newcommand\TmpMacroName[1]{%
+ \texorpdfstring{\cs{#1}}{\textbackslash #1}%
+}
+
+\DefineVerbatimEnvironment{code}{Verbatim}
+{
+ frame=single,
+ fontsize=\footnotesize,
+}
+
+\newcommand{\TmpLuaFunction}[1]{
+ \marginpar{%
+ \raggedleft%
+ \MacroFont%
+ \texttt{%
+ \scantokens{\catcode`\_=12\relax#1}%
+ }%
+ }%
+}
+
+\begin{document}
+
+\providecommand*{\url}{\texttt}
+\GetFileInfo{nodetree.dtx}
+\title{The \textsf{nodetree} package}
+\author{%
+ Josef Friedrich\\%
+ \url{josef@friedrich.rocks}\\%
+ \href{https://github.com/Josef-Friedrich/nodetree}{github.com/Josef-Friedrich/nodetree}\\%
+ with contributions by Werner Lemberg
+}
+\date{v2.3.0 from 2023/09/10}
+
+\maketitle
+
+\begin{NodetreeEmbedEnv}
+nodetree
+\end{NodetreeEmbedEnv}
+
+\newpage
+
+\tableofcontents
+
+\newpage
+
+%-----------------------------------------------------------------------
+% Abstract
+%-----------------------------------------------------------------------
+
+\section{Abstract}
+
+|nodetree| is a Lua\TeX{} development package for both plain \TeX{}
+and \LaTeX{} that visualizes the structure of node lists while
+compiling with the \TeX{} engine. It uses a visual representation of
+node lists similar to the UNIX |tree| command’s output for folder
+trees. The processed document isn’t changed.
+
+The tree view can be emitted to the console, to a log file, or as a
+\LaTeX{} input file. Its appearance is highly customizable; multiple
+color and B/W themes together with various levels of verbosity are
+provided.
+
+Node lists are the main building blocks of the \TeX{} engine, which
+Lua\TeX{} allows to inspect and modify. |nodetree| is inspired by a
+\href{https://gist.github.com/pgundlach/556247} {gist from Patrick
+ Gundlach}.
+
+%-----------------------------------------------------------------------
+% Usage
+%-----------------------------------------------------------------------
+
+\section{Usage}
+
+The package |nodetree| has four usage scenarios.
+It can be used as a standalone Lua module, as a plain Lua\TeX{}, a
+Lua\LaTeX{} package or as package to embed nodetree views in a
+Lua\LaTeX{} document.
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\newpage
+
+\subsection{As a plain Lua\TeX{} package}
+
+Run |luatex luatex-test.tex| for example to list the nodes using
+Lua\TeX{}.
+
+\begin{minted}{latex}
+\input{nodetree.tex}
+\NodetreeRegisterCallback{postline}
+
+Lorem ipsum dolor.
+\bye
+\end{minted}
+
+\subsubsection{Available macros}
+
+\def\TmpTabularMacrosPlainTeX{
+\cmd{\NodetreeRegisterCallback}\marg{callbacks} &
+\TmpPageSecRef{sec:cmd:nodetree-register-callback} \\
+
+\cmd{\NodetreeUnregisterCallback}\marg{callbacks} &
+\TmpPageSecRef{sec:cmd:nodetree-unregister-callback} \\
+
+\cmd{\NodetreeSetOption}\oarg{option}\marg{value} &
+\TmpPageSecRef{sec:cmd:nodetree-set-option} \\
+
+\cmd{\NodetreeResetOption}\marg{option} &
+\TmpPageSecRef{sec:cmd:nodetree-reset-option} \\
+
+\cmd{\NodetreeReset} &
+\TmpPageSecRef{sec:cmd:nodetree-reset} \\
+}
+
+\begin{tabular}{ll}
+\textbf{Macro name} &
+\textbf{Reference} \\
+
+\TmpTabularMacrosPlainTeX
+
+\end{tabular}
+
+\subsubsection{Available options}
+
+\def\TmpTabularOptionsBase{\scantextokens{
+|callback| &
+\TmpPageSecRef{sec:option:callback} \\
+
+|verbosity| &
+\TmpPageSecRef{sec:option:verbosity} \\
+
+|color| &
+\TmpPageSecRef{sec:option:color} \\
+
+|unit| &
+\TmpPageSecRef{sec:option:unit} \\
+
+|decimalplaces| &
+\TmpPageSecRef{sec:option:decimalplaces} \\
+}}
+
+\begin{tabular}{ll}
+\textbf{Option name} &
+\textbf{Reference} \\
+
+\TmpTabularOptionsBase
+
+|channel| &
+\TmpPageSecRef{sec:option:channel} \\
+\end{tabular}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\newpage
+
+\subsection{As a Lua\LaTeX{} package}
+
+Run |lualatex lualatex-test.tex| to show a node tree using
+Lua\LaTeX{}. In Lua\LaTeX{} you can omit a call to
+|\NodetreeRegisterCallback{postline}|, since |\usepackage{nodetree}|
+registers the |post_linebreak_filter| by default. Use
+|\NodetreeUnregisterCallback{postline}| if you don’t want to debug the
+|post_linebreak_filter|.
+
+\begin{minted}{latex}
+\documentclass{article}
+\usepackage{nodetree}
+
+\begin{document}
+Lorem ipsum dolor.
+\end{document}
+\end{minted}
+
+\subsubsection{Available macros}
+
+\begin{tabular}{ll}
+\textbf{Macro name} &
+\textbf{Reference} \\
+
+\TmpTabularMacrosPlainTeX
+
+\cmd{\NodetreeSet}\marg{kv-options} &
+\TmpPageSecRef{sec:cmd:nodetree-set} \\
+\end{tabular}
+
+\subsubsection{Available options}
+
+\begin{tabular}{ll}
+\textbf{Option name} &
+\textbf{Reference} \\
+
+\TmpTabularOptionsBase
+
+|channel| &
+\TmpPageSecRef{sec:option:channel} \\
+\end{tabular}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\newpage
+
+\subsection{As a Lua module}
+
+Import the Lua module of the package inside
+\mintinline{latex}{\directlua{}}
+with this command:
+\mintinline{lua}{local nodetree = require('nodetree')}.
+Then use the Lua function
+\mintinline{lua}{nodetree.print(head, options)}
+to debug nodes inside your Lua code.
+
+\begin{minted}{lua}
+local nodetree = require('nodetree')
+
+local rule1 = node.new('rule')
+rule1.width = 20 * 65536
+rule1.height = 10 * 65536
+rule1.depth = 10 * 65536
+nodetree.print(vbox)
+\end{minted}
+
+\noindent
+The function \mintinline{lua}{nodetree.print()} takes as a second
+argument a Lua table to configure the output.
+
+\begin{minted}{lua}
+nodetree.print(vbox, { verbosity = 2, unit = 'cm' })
+\end{minted}
+
+\noindent
+These are the default options:
+
+\begin{minted}{lua}
+options = {
+ callback = 'post_linebreak_filter',
+ channel = 'term',
+ color = 'colored',
+ decimalplaces = 2,
+ unit = 'pt',
+ verbosity = 0,
+ firstline = 1,
+ lastline = -1,
+}
+\end{minted}
+
+Options |firstline| and |lastline| only have an effect on function
+\mintinline{lua}{nodetree.input(filename)}, which is used to implement
+\cmd{\NodetreeEmbedInput} \TmpSecRef{sec:cmd:nodetree-embed-input}.
+
+The following code snippet demonstrates the usage in Lua\TeX{}.
+|head| is the current node.
+
+\begin{minted}{latex}
+\directlua{
+ local nodetree = require('nodetree')
+ local test = function (head)
+ nodetree.print(head)
+ end
+ callback.register('post_linebreak_filter', test)
+}
+
+Lorem ipsum dolor.
+\bye
+\end{minted}
+
+\noindent
+This example illustrates how the function has to be applied in
+Lua\LaTeX{}.
+
+\begin{minted}{latex}
+\documentclass{article}
+\usepackage{nodetree}
+
+\begin{document}
+
+\directlua{
+ local nodetree = require('nodetree')
+ local test = function (head)
+ nodetree.print(head)
+ end
+ luatexbase.add_to_callback('post_linebreak_filter', test, 'test')
+}
+
+Lorem ipsum dolor.
+\end{document}
+\end{minted}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\newpage
+\subsection{The package \texttt{nodetree-embed}}
+
+The single purpose of this auxiliary package is to provide a view
+similar to a terminal (console) output.
+This view mimics the output
+of |nodetree| in a terminal.
+The view can be embedded in a Lua\LaTeX{} file. You have to
+compile documents using this embedded view with the option
+|--shell-escape|.
+The main environment of this package is |NodetreeEmbed|.
+Markup
+inside this environment is written into a temporary \LaTeX{} file.
+This file is compiled in the background by |latexmk| and the
+|nodetree| output is embedded into this view.
+The following list shows each intermediate step:
+
+\begin{enumerate}
+
+\item |jobname.tex|
+
+\begin{minted}{latex}
+\begin{NodetreeEmbedEnv}
+nodetree
+\end{NodetreeEmbedEnv}
+\end{minted}
+
+\item |_nodetree-jobname/1.tex|
+
+\begin{minted}{latex}
+%!TEX program = lualatex
+\documentclass{article}
+\usepackage{nodetree}
+\NodetreeSetOption[channel]{tex}
+\NodetreeSetOption[verbosity]{0}
+\NodetreeSetOption[unit]{pt}
+\NodetreeSetOption[decimalplaces]{2}
+\NodetreeUnregisterCallback{post_linebreak_filter}
+\NodetreeRegisterCallback{post_linebreak_filter}
+\begin{document}
+nodetree
+\end{document}
+\end{minted}
+
+\item |_nodetree-jobname/1.nttex|: This temporary Lua\LaTeX{} file is
+compiled using |latexmk| and embedded in the environment |NodetreeEmbed|
+(the trailing |\| character indicates line continuation).
+
+\begin{minted}{latex}
+Callback: \textcolor{NTEred}{post\_linebreak\_filter}\par
+------------------------------------------\par
+\mbox{├─\textcolor{NTEmagentabright}{GLUE}\hspace{0.5em}(baselineskip)\
+ \textcolor{NTEyellow}{wd} 5.06\textcolor{NTEwhite}{pt}}\par
+...
+\end{minted}
+
+\item Finally the result:
+
+\begin{NodetreeEmbedEnv}
+nodetree
+\end{NodetreeEmbedEnv}
+
+\end{enumerate}
+
+\subsubsection{Available macros}
+
+\begin{tabular}{ll}
+\textbf{Macro name} &
+\textbf{Reference} \\
+
+\TmpTabularMacrosPlainTeX
+
+\cmd{\NodetreeSet}\marg{kv-options} &
+\TmpPageSecRef{sec:cmd:nodetree-set} \\
+
+\cmd{\NodetreeEmbedCmd}\oarg{kv-options}\marg{tex-markup} &
+\TmpPageSecRef{sec:cmd:nodetree-embed-cmd} \\
+
+\cmd{\NodetreeEmbedInput}\oarg{kv-options}\marg{nttex-file} &
+\TmpPageSecRef{sec:cmd:nodetree-embed-input} \\
+\end{tabular}
+
+\subsubsection{Available environment}
+
+\begin{tabular}{ll}
+\textbf{Environment name} &
+\textbf{Reference} \\
+
+|\begin{NodetreeEmbedEnv}|\oarg{kv-options} & % |\end{NodetreeEmbedEnv}|
+\TmpPageSecRef{sec:env:nodetree-embed-env} \\
+\end{tabular}
+
+\subsubsection{Available options}
+
+\begin{tabular}{ll}
+\textbf{Option name} &
+\textbf{Reference} \\
+
+\TmpTabularOptionsBase
+
+|theme| &
+\TmpPageSecRef{sec:option:theme} \\
+
+|thememode| &
+\TmpPageSecRef{sec:option:thememode} \\
+
+|font| &
+\TmpPageSecRef{sec:option:font} \\
+
+|fontsize| &
+\TmpPageSecRef{sec:option:fontsize} \\
+
+|firstline| &
+\TmpPageSecRef{sec:option:firstlastline} \\
+
+|lastline| &
+\TmpPageSecRef{sec:option:firstlastline} \\
+\end{tabular}
+
+%-----------------------------------------------------------------------
+% Macros
+%-----------------------------------------------------------------------
+\newpage
+\section{Macros}
+
+%%
+% \NodetreeRegisterCallback
+%%
+
+\subsection{\TmpMacroName{NodetreeRegisterCallback}}
+\label{sec:cmd:nodetree-register-callback}
+
+\DescribeMacro{\NodetreeRegisterCallback}
+\cmd{\NodetreeRegisterCallback}\marg{callbacks}: Globally register
+\marg{callbacks}, which is a comma-separated list of callback aliases
+\TmpSecRef{sec:option:callback}.
+
+%%
+% \NodetreeUnregisterCallback
+%%
+
+\subsection{\TmpMacroName{NodetreeUnregisterCallback}}
+\label{sec:cmd:nodetree-unregister-callback}
+
+\DescribeMacro{\NodetreeUnregisterCallback}
+\cmd{\NodetreeUnregisterCallback}\marg{callbacks}: Globally unregister
+\marg{callbacks}, which is a separated list of callback aliases
+\TmpSecRef{sec:option:callback}.
+
+%%
+% \NodetreeSetOption
+%%
+
+\subsection{\TmpMacroName{NodetreeSetOption}}
+\label{sec:cmd:nodetree-set-option}
+
+\DescribeMacro{\NodetreeSetOption}
+\cmd{\NodetreeSetOption}\oarg{option}\marg{value}: Globally set a
+single \oarg{option} to \marg{value} \TmpSecRef{sec:options}.
+
+%%
+% \NodetreeResetOption
+%%
+
+\subsection{\TmpMacroName{NodetreeResetOption}}
+\label{sec:cmd:nodetree-reset-option}
+
+\DescribeMacro{\NodetreeResetOption}
+\cmd{\NodetreeResetOption}\marg{option}: Globally reset a single
+\marg{option} to its default value \TmpSecRef{sec:options}.
+
+%%
+% \NodetreeSet
+%%
+
+\subsection{\TmpMacroName{NodetreeSet}}
+\label{sec:cmd:nodetree-set}
+
+\DescribeMacro{\NodetreeSet}
+\cmd{\NodetreeSet}\marg{kv-options}: Globally set multiple options at
+once. It can only be used along with Lua\LaTeX{}. \marg{kv-options}
+are key-value pairs.
+
+\begin{code}
+\NodetreeSet{color=no,callbacks={hpack,vpack},verbosity=2}
+\end{code}
+
+%%
+% \NodetreeReset
+%%
+
+\subsection{\TmpMacroName{NodetreeReset}}
+\label{sec:cmd:nodetree-reset}
+
+\DescribeMacro{\NodetreeReset}
+\cmd{\NodetreeReset}: Globally reset multiple options to their default
+values.
+
+%%
+%
+%%
+
+\subsection{\TmpMacroName{NodetreeEmbedCmd}}
+\label{sec:cmd:nodetree-embed-cmd}
+
+\DescribeMacro{\NodetreeEmbedCmd}
+\cmd{\NodetreeEmbedCmd}\oarg{kv-options}\marg{tex-markup}:
+
+Main macro (cmd) to evaluate some \TeX{} markup and generate a
+node tree from it. See environment version
+\TmpSecRef{sec:cmd:nodetree-embed-cmd}. Uses |xparse|'s |+v| option to
+grab the verbatim content. \marg{kv-options} are key-value pairs and
+set locally only.
+
+Only available in package |nodetree-embed|; you need option
+|--shell-escape|.
+
+%%
+% \NodetreeEmbedInput
+%%
+
+\subsection{\TmpMacroName{NodetreeEmbedInput}}
+\label{sec:cmd:nodetree-embed-input}
+
+\DescribeMacro{\NodetreeEmbedInput}
+\cmd{\NodetreeEmbedInput}\oarg{kv-options}\marg{nttex-file}: The path or
+file name of the |*.nttex| file without the extension.
+\marg{kv-options} are key-value pairs and set locally only.
+
+Only available in package |nodetree-embed|. This command works without
+option |--shell-escape|.
+
+%-----------------------------------------------------------------------
+% Environments
+%-----------------------------------------------------------------------
+
+\newpage
+\section{Environments}
+
+\subsection{\texttt{NodetreeEmbedEnv}}
+\label{sec:env:nodetree-embed-env}
+
+\DescribeEnv{NodetreeEmbedEnv}
+|\begin{NodetreeEmbedEnv}|\oarg{kv-options}\\
+\dots{} \textit{\TeX{} markup for evaluation} \dots\\
+|\end{NodetreeEmbedEnv}|
+
+Main environment (env) to evaluate some \TeX{} markup and generate a
+node tree from it. See command version
+\TmpSecRef{sec:cmd:nodetree-embed-cmd}. Uses the \cmd{\detokenize}
+command to grab the verbatim content. \marg{kv-options} are key-value
+pairs and set locally only.
+
+Only available in package |nodetree-embed|; you need option
+|--shell-escape|.
+
+%-----------------------------------------------------------------------
+% Options
+%-----------------------------------------------------------------------
+\newpage
+\section{Options}
+\label{sec:options}
+
+%%
+% callback
+%%
+
+\subsection{Option \texttt{callback}}
+\label{sec:option:callback}
+
+The option |callback| is the most important setting of the package. It
+is possible to specify an alias to select the callback. Take a look at
+the overview of callbacks (\rightarrow{} Figure~\ref{fig:callback}).
+|nodetree| supports all node-related callbacks as listed in the
+Lua\TeX{} reference manual.
+
+These macros process callback options:
+
+\begin{quote}
+ \cmd{\NodetreeRegisterCallback}\marg{callbacks}\\
+ \cmd{\NodetreeUnregisterCallback}\marg{callbacks}\\
+ \cmd{\NodetreeSet}\marg{callback=<callbacks>}\\
+ \cmd{\usepackage}\oarg{callback=<callbacks>}\marg{nodetree}
+\end{quote}
+
+The |nodetree| package can watch the node tree before and after the
+functions of a callback are executed: It is possible to prepend and/or
+append a colon (|:|) to indicate the desired watchpoint position,
+which defaults to 'before' if no colon is used.
+
+Use commas to specify multiple callbacks; trailing and leading
+whitespace is ignored. For example, this call
+
+\begin{code}
+\NodetreeRegisterCallback{:preline, line, :postline:}
+\end{code}
+
+\noindent
+watches the node tree before the |preline| callback functions, before
+the |line| callback functions, and before and after the |postline|
+callback functions. In case there are no callback functions
+registered for one of the |hyphenate|, |kerning|, |ligaturing|, and
+|mlist_to_hlist| callbacks, Lua\TeX{} executes some internal code
+instead. It thus makes sense to watch the node tree before and after
+these (empty) callbacks even in this case.
+
+Wrap your callback aliases in curly braces for the macro
+|\NodetreeSet|. Note that no whitespace between |=| and |{| is
+allowed.
+
+\begin{code}
+\NodetreeSet{callback={:preline, line, :postline:}}
+\end{code}
+
+The same applies for the macro |\usepackage|:
+
+\begin{code}
+\usepackage{callback={:preline, line, :postline:}}
+\end{code}
+
+The callbacks in Figure~\ref{fig:callback} are listed in the same
+order as in the Lua\TeX{} reference manual. Note that the |ligaturing|
+and |kerning| callbacks only have an effect on ligatures and kernings,
+respectively, if the |luaotfload| package (which is the default for
+Lua\LaTeX{}, and an optional package for Lua\TeX{}) handles the
+affected font with |mode=base| (see the
+\href{http://mirrors.ctan.org/macros/luatex/generic/luaotfload/luaotfload-latex.pdf}
+{documentation} for more details).
+
+%%
+% Tabular callbacks
+%%
+
+\newcommand{\TmpCallbackRow}[3]{
+ \TmpLuaCodeInline{#1} & \TmpLuaCodeInline{#2} & \TmpLuaCodeInline{\footnotesize#3} \\
+}
+
+\begin{figure}
+\begin{tabular}{lll}
+\textbf{Callback} & \textbf{Alias} & \textbf{Alias (longer)} \\
+\TmpCallbackRow{contribute_filter}
+{contribute}
+{contributefilter}
+
+\TmpCallbackRow{buildpage_filter}
+{buildfilter} %
+{buildpagefilter}
+
+% new
+\TmpCallbackRow{build_page_insert}
+{buildinsert}
+{buildpageinsert}
+
+\TmpCallbackRow{pre_linebreak_filter}
+{preline}
+{prelinebreakfilter}
+
+\TmpCallbackRow{linebreak_filter}
+{line}
+{linebreakfilter}
+
+\TmpCallbackRow{append_to_vlist_filter}
+{append}
+{appendtovlistfilter}
+
+\TmpCallbackRow{post_linebreak_filter}
+{postline}
+{postlinebreakfilter}
+
+\TmpCallbackRow{hpack_filter}
+{hpack}
+{hpackfilter}
+
+\TmpCallbackRow{vpack_filter}
+{vpack}
+{vpackfilter}
+
+\TmpCallbackRow{hpack_quality}
+{hpackq}
+{hpackquality}
+
+\TmpCallbackRow{vpack_quality}
+{vpackq}
+{vpackquality}
+
+\TmpCallbackRow{process_rule}
+{process}
+{processrule}
+
+\TmpCallbackRow{pre_output_filter}
+{preout}
+{preoutputfilter}
+
+\TmpCallbackRow{hyphenate}
+{hyph}
+{}
+
+\TmpCallbackRow{ligaturing}
+{liga}
+{}
+
+\TmpCallbackRow{kerning}
+{kern}
+{}
+
+\TmpCallbackRow{insert_local_par}
+{insert}
+{insertlocalpar}
+
+\TmpCallbackRow{mlist_to_hlist}
+{mhlist}
+{mlisttohlist}
+\end{tabular}
+
+\caption{The callback aliases}
+\label{fig:callback}
+\end{figure}
+
+%%
+% channel
+%%
+
+\subsection{Option \texttt{channel}}
+\label{sec:option:channel}
+
+You can select the debug output channel with this option. The default
+value for the option |channel| is |term|, which displays the node tree in
+the current terminal. Specify |log| and the package creates a log file
+named |<jobname>.ntlog|. Specify |tex| and a log file named
+|<jobname>.nttex| is created. |nt...| stands for |nodetree|.
+|<jobname>| is the basename of your file you want to debug. The debug
+channel is only useful for the auxiliary package |nodetree-embed|. Paste
+the markup in the environment |NodetreeEmbedView| and you get a
+terminal-like view in your document.
+
+%%
+% verbosity
+%
+
+\subsection{Option \texttt{verbosity}}
+\label{sec:option:verbosity}
+
+Higher integer values result in a more verbose output. The default value
+for this option is~|0|. At the moment verbosity levels |0| to~|3| are
+implemented.
+
+\def\TmpExampleVerbosity#1{
+ \subsubsection{Example: \texttt{verbosity=#1}}
+ \begin{NodetreeEmbedEnv}[verbosity=#1,callback=pre_linebreak_filter,
+ fontsize=\fontsize{5.5}{6.6}\selectfont]
+ .
+ \end{NodetreeEmbedEnv}
+}
+
+\TmpExampleVerbosity{0}
+\TmpExampleVerbosity{1}
+\TmpExampleVerbosity{2}
+\TmpExampleVerbosity{3}
+
+%%
+% color
+%%
+
+\subsection{Option \texttt{color}}
+\label{sec:option:color}
+
+The default option for |color| is |colored|. Use any other string (for
+example |none| or |no|) to disable the colored terminal output of the
+package.
+
+\begin{code}
+\usepackage[color=no]{nodetree}
+\end{code}
+
+%%
+% unit
+%%
+
+\subsection{Option \texttt{unit}}
+\label{sec:option:unit}
+
+The option |unit| sets the length unit to display all length values of
+the nodes. The default option for |unit| is |pt|. See figures
+\ref{fig:fixed-units} and~\ref{fig:relative-units} for possible values.
+
+\begin{figure}
+\begin{tabular}{lp{10cm}}
+\textbf{Unit} &
+\textbf{Description} \\
+
+pt &
+Point 1/72.27 inch. The conversion to metric units, to two decimal
+places, is 1 point = 2.85 mm = 28.45 cm. \\
+
+pc &
+Pica, 12 pt \\
+
+in &
+Inch, 72.27 pt \\
+
+bp &
+Big point, 1/72 inch. This length is the definition of a point in
+PostScript and many desktop publishing systems. \\
+
+cm &
+Centimeter \\
+
+mm &
+Millimeter \\
+
+dd &
+Didot point, 1.07 pt \\
+
+cc &
+Cicero, 12 dd \\
+
+sp &
+Scaled point, 1/65536 pt \\
+\end{tabular}
+\caption{Fixed units}
+\label{fig:fixed-units}
+\end{figure}
+
+\begin{figure}
+\begin{tabular}{lp{10cm}}
+\textbf{Unit} &
+\textbf{Description} \\
+
+ex &
+x-height of the current font \\
+
+em &
+Width of the capital letter M \\
+\end{tabular}
+\caption{Relative units}
+\label{fig:relative-units}
+\end{figure}
+
+\def\TmpExampleUnit#1{
+ \subsubsection{Example: \texttt{unit=#1}}
+ \begin{NodetreeEmbedEnv}[unit=#1,callback=pre_linebreak_filter]
+ Lorem.
+ \end{NodetreeEmbedEnv}
+}
+
+\TmpExampleUnit{pt}
+\TmpExampleUnit{sp}
+\TmpExampleUnit{cm}
+
+%%
+% decimalplaces
+%%
+
+\subsection{Option \texttt{decimalplaces}}
+\label{sec:option:decimalplaces}
+
+The options |decimalplaces| sets the number of decimal places for some
+node fields. If |decimalplaces| is set to |0| only integer values are shown.
+
+\begin{code}
+\NodetreeSetOption[decimalplaces]{4}
+\end{code}
+
+\def\TmpExampleDecimalplaces#1{
+ \subsubsection{Example: \texttt{decimalplaces=#1}}
+ \begin{NodetreeEmbedEnv}[unit=cc,decimalplaces=#1,callback=pre_linebreak_filter]
+ Lorem.
+ \end{NodetreeEmbedEnv}
+}
+
+\TmpExampleDecimalplaces{0}
+\TmpExampleDecimalplaces{2}
+\TmpExampleDecimalplaces{5}
+
+%%
+% theme and thememode
+%%
+
+\def\TmpExampleTheme#1#2{
+ \subsubsection{Example: \texttt{theme=#1} \texttt{thememode=#2}}
+ \begin{NodetreeEmbedEnv}[callback=pre_linebreak_filter,theme=#1,thememode=#2,fontsize=\small]
+ .
+ \end{NodetreeEmbedEnv}
+}
+
+\subsection{Option \texttt{theme} and \texttt{thememode}}
+\label{sec:option:theme}
+\label{sec:option:thememode}
+
+% bw
+\TmpExampleTheme{bwdark}{dark}
+\TmpExampleTheme{bwlight}{light}
+
+% monokaisoda
+\TmpExampleTheme{monokaisoda}{dark}
+\TmpExampleTheme{monokaisoda}{light}
+
+%%
+% font
+%%
+
+\subsection{Option \texttt{font}}
+\label{sec:option:font}
+
+\NodetreeSet{fontsize=\footnotesize}
+
+\def\TmpExampleFont#1{
+ \subsubsection{Example: \texttt{font=\{#1\}}}
+ \begin{NodetreeEmbedEnv}[font={#1}]
+ .
+ \end{NodetreeEmbedEnv}
+}
+
+|nodetree-embed| passes the option |font| down to the
+command |\setmonofont{}| of the |fontspec| package. The used font
+should be monospaced and have some box drawing glyphs (see
+table~\ref{fig:unicode}).
+
+\TmpExampleFont{Liberation Mono}
+\TmpExampleFont{Ubuntu Mono}
+
+%%
+% fontsize
+%%
+
+\subsection{Option \texttt{fontsize}}
+\label{sec:option:fontsize}
+
+\def\TmpExampleFontSize#1{
+ \subsubsection{Example: \TmpMacroName{#1}}
+ \begin{NodetreeEmbedEnv}[callback=pre_linebreak_filter,
+ fontsize=\csname #1\endcsname]
+ .
+ \end{NodetreeEmbedEnv}
+}
+
+\TmpExampleFontSize{small}
+\TmpExampleFontSize{tiny}
+
+\subsection{Options \texttt{firstline} and \texttt{lastline}}
+\label{sec:option:firstlastline}
+
+These two options are for function \cmd{\NodetreeEmbedInput} only
+\TmpSecRef{sec:cmd:nodetree-embed-input}. They specify the first
+and last shown line of the read |*.nttex| file. Values |1|, |2|,
+\ldots, corresponds to the first line, second, line, etc. Values
+|-1|, |-2|, \ldots, correspond to the last line, the line before the
+last line, etc. The default values are |firstline = 1| and
+|lastline = -1| to display the whole file.
+
+%-----------------------------------------------------------------------
+% Visual tree structure
+%-----------------------------------------------------------------------
+\newpage
+\section{Visual tree structure}
+
+%%
+% Two different connections
+%%
+
+\subsection{Two different connections}
+
+Nodes in Lua\TeX{} are connected. The |nodetree| package distinguishes
+between \emph{list} and \emph{field} connections.
+
+\begin{itemize}
+ \item list: Nodes that are doubly connected by |next| and
+ |previous| fields.
+ \item field: Connections to nodes by other fields than |next| and
+ |previous|, for example, using |head| and |pre|.
+\end{itemize}
+
+%%
+% Unicode characters
+%%
+
+\subsection{Unicode characters to show the tree view}
+
+\renewcommand{\arraystretch}{1.5}
+
+The package |nodetree| uses the unicode box drawing symbols. Your
+default terminal font should contain this characters to obtain the tree
+view. Eight box drawing characters are necessary.
+
+\begin{figure}
+{
+\fontspec{DejaVu Sans Mono}
+\begin{tabular}{lcl}
+\textbf{Code} & \textbf{Character} & \textbf{Name} \\
+U+2500 & ─ & BOX DRAWINGS LIGHT HORIZONTAL \\
+U+2502 & │ & BOX DRAWINGS LIGHT VERTICAL \\
+U+2514 & └ & BOX DRAWINGS LIGHT UP AND RIGHT \\
+U+251C & ├ & BOX DRAWINGS LIGHT VERTICAL AND RIGHT \\
+U+2550 & ═ & BOX DRAWINGS DOUBLE HORIZONTAL \\
+U+2551 & ║ & BOX DRAWINGS DOUBLE VERTICAL \\
+U+255A & ╚ & BOX DRAWINGS DOUBLE UP AND RIGHT \\
+U+2560 & ╠ & BOX DRAWINGS DOUBLE VERTICAL AND RIGHT \\
+\end{tabular}
+}
+\caption{The Unicode box drawings glyphs}
+\label{fig:unicode}
+\end{figure}
+
+\noindent
+For |list| connections \emph{light} characters are shown.
+
+{
+\setmonofont{DejaVu Sans Mono}
+\begin{code}
+│ │
+│ ├─list1
+│ └─list2
+└─list3
+\end{code}
+}
+
+\noindent
+|field| connections are visialized by \emph{Double} characters.
+
+{
+\setmonofont{DejaVu Sans Mono}
+\begin{code}
+║ ║
+║ ╠═field1
+║ ╚═field2
+╚═field3
+\end{code}
+}
+
+%-----------------------------------------------------------------------
+% Examples
+%-----------------------------------------------------------------------
+\newpage
+\section{Examples}
+
+This section lists some examples of the |nodetree| output.
+
+%%
+% packagename
+%%
+
+\subsection{The node list of the package name}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true,callback=post_linebreak_filter]
+nodetree
+\end{NodetreeEmbedEnv}
+
+%%
+% math
+%%
+
+\subsection{The node list of a mathematical formula}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true,callback=post_linebreak_filter]
+$1+2$
+\end{NodetreeEmbedEnv}
+
+%%
+% ligatures
+%%
+
+\subsection{The node list of the word \emph{Office}}
+
+The characters \emph{ffi} are deeply nested in a discretionary node.
+
+\begin{NodetreeEmbedEnv}[showmarkup=true,decimalplaces=0]
+Office
+\end{NodetreeEmbedEnv}
+
+%-----------------------------------------------------------------------
+% Node types
+%-----------------------------------------------------------------------
+
+\section{Node types}
+
+This section shows some node types in a |nodetree| view.
+
+\newcommand{\TmpHeadingNodeTypeSub}[4]{
+ \subsection{Type \texttt{#1(#2)}, subtype \texttt{#3(#4)}}
+}
+
+\newcommand{\TmpNodeTypeSub}[6]{
+ \subsection{Type \texttt{#1(#2)}, subtype \texttt{#3\_#4(#5)}}
+ \TmpVerbExample{#2#1#5#3#4}{#6}
+}
+
+\newcommand{\TmpHeadingNodeType}[2]{
+ \subsection{Type \texttt{#1(#2)}}
+}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{hlist}{0}{line}{1}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+Lorem
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{hlist}{0}{box}{2}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+L\hbox to 40pt{ore}m
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{hlist}{0}{indent}{3}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true,unit=cm]
+\setlength{\parindent}{5cm}
+I
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeType{vlist}{1}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true,decimalplaces=1]
+L\vbox to 40pt{O}L
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeType{rule}{2}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true,unit=mm]
+\rule[-2mm]{10mm}{4mm}
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeType{mark}{4}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true,callback=pre_output_filter]
+\mark{Lorem}.
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{disc}{7}{discretionary}{0}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+L\discretionary{}{}{}L
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{disc}{7}{explicit}{1}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+L\-O\-L
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{disc}{7}{regular}{3}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true,decimalplaces=0]
+Office
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpNodeTypeSub{whatsit}{8}{pdf}{action}{22}{\fontsize{5.5}{6.6}\selectfont}
+\TmpNodeTypeSub{whatsit}{8}{pdf}{colorstack}{28}{\footnotesize}
+
+%%
+%
+%%
+
+\TmpHeadingNodeType{dir}{10}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+\textdir TRT nur {\textdir TLT run \textdir TRT NUR} nur
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{glue}{12}{baselineskip}{2}
+
+\NodetreeEmbedCmd[showmarkup=true,unit=cm]{
+\baselineskip=5cm
+Lorem
+
+Lorem
+}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{glue}{12}{parskip}{3}
+
+\NodetreeEmbedCmd[showmarkup=true,callback=pre_output_filter]{
+\parskip=5cm
+Lorem
+
+Lorem
+}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{glue}{12}{spaceskip}{13}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+\spaceskip=5cm
+a a
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{glue}{12}{leaders}{100}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+a \leavevmode\leaders\hbox{ . }\hfill\kern0pt a
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{glue}{12}{cleaders}{101}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+a \leavevmode\cleaders\hbox{ . }\hfill\kern0pt a
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{glue}{12}{xleaders}{102}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+a \leavevmode\xleaders\hbox{ . }\hfill\kern0pt a
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{glue}{12}{gleaders}{102}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+a \leavevmode\gleaders\hbox{ . }\hfill\kern0pt a
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{kern}{13}{userkern}{0}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+a\kern2pt
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{kern}{13}{fontkern}{1}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+Ve
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{kern}{13}{accentkern}{2}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+\accent96 a
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeTypeSub{kern}{13}{italiccorrection}{3}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+\textit{L}\/OL
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeType{penalty}{14}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+L \penalty 23 OL
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeType{glyph}{29}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true]
+abc
+\end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+% It seems that 'attribute' nodes don't appear in node trees.
+%
+% \TmpHeadingNodeType{attribute}{38}
+%
+% \begin{NodetreeEmbedEnv}[showmarkup=true]
+% {\attribute0=1 A}
+% \end{NodetreeEmbedEnv}
+
+%%
+%
+%%
+
+\TmpHeadingNodeType{attributelist}{40}
+
+\begin{NodetreeEmbedEnv}[showmarkup=true,callback=hpackfilter]
+{\attribute0=1 A}
+\end{NodetreeEmbedEnv}
+
+\DocInput{nodetree.dtx}
+
+\subsection{The file \texttt{nodetree.lua}}
+
+% Compilation failure:
+% lualatex: ../../../texk/web2c/luatexdir/lang/texlang.c:986: hnj_hyphenation: Assertion `(((varmem[(wordstart)].hh.u.B1) & (1 << 0)) && !((varmem[(wordstart)].hh.u.B1) & (1 << 1) ) && !((varmem[(wordstart)].hh.u.B1) & (1 << 2) ))' failed
+% \inputminted{lua}{nodetree.lua}
+There is a source code documentation of the file nodetree.lua compiled
+with Ldoc on Github:
+\url{http://josef-friedrich.github.io/nodetree/}
+
+\end{document}
diff --git a/macros/luatex/generic/nodetree/nodetree.dtx b/macros/luatex/generic/nodetree/nodetree.dtx
index 764c9684cf..0b3668a8ce 100644
--- a/macros/luatex/generic/nodetree/nodetree.dtx
+++ b/macros/luatex/generic/nodetree/nodetree.dtx
@@ -1,6 +1,6 @@
% \iffalse meta-comment
%
-% Copyright (C) 2016-2022 by Josef Friedrich <josef@friedrich.rocks>
+% Copyright (C) 2016-2023 by Josef Friedrich <josef@friedrich.rocks>
% ----------------------------------------------------------------------
% This work may be distributed and/or modified under the conditions of
% the LaTeX Project Public License, either version 1.3 of this license
@@ -28,7 +28,7 @@
%<package>\NeedsTeXFormat{LaTeX2e}[1999/12/01]
%<package>\ProvidesPackage{nodetree}
%<*package>
- [2022/12/17 v2.2.1 Visualize node lists in a tree view]
+ [2023/09/10 v2.3.0 Visualize node lists in a tree view]
%</package>
% \fi
%
@@ -50,34 +50,6 @@
% Grave accent \` Left brace \{ Vertical bar \|
% Right brace \} Tilde \~}
%
-%
-% \changes{v0.1}{2015/06/16}{Converted to DTX file}
-% \changes{v1.0}{2016/07/07}{Inital release}
-% \changes{v1.1}{2016/07/13}{Fix the registration of same callbacks}
-% \changes{v1.2}{2016/07/18}{Fix difference between README.md in the upload and that from nodetree.dtx}
-% \changes{v2.0}{2020/05/29}{
-% * Switch from lowercase macro names to PascalCase names for better readability.
-% * The Lua code is no longer developed inside the DTX file, instead in a separate file named nodetree.lua.
-% * Add a sub package named nodetree-embed.sty for embedding nodetree views into a \LaTeX{} document.
-% * Add support for new node subtype names.
-% * Add support for a new Lua\TeX{} node callback.
-% * Add support for node properties.
-% * Less verbose representation of node attributes.
-% * Minor tree output adjustments.
-% }
-% \changes{v2.1}{2020/10/03}{
-% * Make the package compatible with the Harfbuzz mode of the luaotfload fontloader.
-% * Print node properties of copied nodes.
-% }
-% \changes{v2.2}{2020/10/23}{
-% * Fix unavailable library error (utf8 not in Lua5.1)
-% }
-% \changes{v2.2.1}{2022/12/17}{
-% * Replace non-printable unicode symbols with ???.
-% * Add missing newlines for callbacks with multiple node lists.
-% * Print subtype fields with value 0.
-% * Fix the presentation of the subtype field of a glyph as a bit field.
-% }
% \DoNotIndex{\newcommand,\newenvironment,\def,\directlua}
%
% \StopEventually{}
@@ -90,7 +62,7 @@
% \MacroTopsep = 10pt plus 2pt minus 2pt
% \MacrocodeTopsep = 10pt plus 1.2pt minus 1pt
% \makeatletter
-% \c@CodelineNo 25 \relax
+% \c@CodelineNo 22 \relax
% \makeatother
%
% \subsection{The file \tt{nodetree.tex}}
@@ -198,7 +170,7 @@
% \end{macrocode}
%
% \begin{macrocode}
-\DeclareStringOption[1]{verbosity}
+\DeclareStringOption[0]{verbosity}
\define@key{NT}{verbosity}[]{\NodetreeSetOption[verbosity]{#1}}
% \end{macrocode}
%
@@ -237,10 +209,16 @@
%</package>
%<*packageembed>
% \fi
+% \makeatletter
+% \c@CodelineNo 22 \relax
+% \makeatother
+%
+% \subsection{The file \tt{nodetree-embed.sty}}
+%
% \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{nodetree-embed}
- [2022/12/17 v2.2.1 Embed node trees into a LaTeX document]
+ [2023/09/10 v2.3.0 Embed node trees into a LaTeX document]
% \end{macrocode}
%
% \begin{macrocode}
@@ -260,18 +238,11 @@
% \end{macrocode}
%
% \begin{macrocode}
-\directlua{
- nodetree = require('nodetree')
- nodetree.check_shell_escape()
-}
-% \end{macrocode}
-%
-% \begin{macrocode}
\define@key{NTE}{callback}[]{\NodetreeSetOption[callback]{#1}}
% \end{macrocode}
%
% \begin{macrocode}
-\DeclareStringOption[1]{verbosity}
+\DeclareStringOption[0]{verbosity}
\define@key{NTE}{verbosity}[]{\NodetreeSetOption[verbosity]{#1}}
% \end{macrocode}
%
@@ -307,6 +278,16 @@
% \end{macrocode}
%
% \begin{macrocode}
+\DeclareStringOption[1]{firstline}
+\define@key{NTE}{firstline}[]{\NodetreeSetOption[firstline]{#1}}
+% \end{macrocode}
+%
+% \begin{macrocode}
+\DeclareStringOption[-1]{lastline}
+\define@key{NTE}{lastline}[]{\NodetreeSetOption[lastline]{#1}}
+% \end{macrocode}
+%
+% \begin{macrocode}
\DeclareBoolOption{showmarkup}
% \end{macrocode}
%
@@ -404,7 +385,7 @@
%
% \begin{macro}{\NodetreeSet}
% Same definition as in nodetree.sty. Only implement this command
-% if not already registers.
+% if not already registered.
% \begin{macrocode}
\providecommand{\NodetreeSet}[1]{%
\setkeys{NTE}{#1}%
@@ -414,6 +395,7 @@
%
% \begin{macrocode}
\newenvironment{NodetreeEmbedView}[1][]{
+ \directlua{nodetree.push_options()}
\setkeys{NTE}{#1}
\NTE@colors
\begin{mdframed}[
@@ -424,17 +406,21 @@
\NTE@fonts
}{
\end{mdframed}%
+ \directlua{nodetree.pop_options()}%
}
% \end{macrocode}
%
% \begin{environment}{NodetreeEmbedEnv}
% \begin{macrocode}
\NewDocumentEnvironment { NodetreeEmbedEnv } { O{} +b } {
+ \directlua{
+ nodetree.check_shell_escape('NodetreeEmbedEnv', false)
+ nodetree.push_options()
+ }
\setkeys{NTE}{#1}
\ifNTEK@showmarkup
\noindent
\texttt{\detokenize{#2}}
- \else
\fi
\NTE@colors
\begin{NodetreeEmbedView}
@@ -442,6 +428,7 @@
nodetree.compile_include('\luaescapestring{\unexpanded{#2}}')
}
\end{NodetreeEmbedView}
+ \directlua{nodetree.pop_options()}
}{}
% \end{macrocode}
% \end{environment}
@@ -449,11 +436,14 @@
% \begin{macro}{\NodetreeEmbedCmd}
% \begin{macrocode}
\NewDocumentCommand { \NodetreeEmbedCmd } { O{} +v } {
+ \directlua{
+ nodetree.check_shell_escape('\string\\NodetreeEmbedCmd', true)
+ nodetree.push_options()
+ }
\setkeys{NTE}{#1}
\ifNTEK@showmarkup
\noindent
\texttt{#2}
- \else
\fi
\NTE@colors
\begin{NodetreeEmbedView}
@@ -461,6 +451,7 @@
nodetree.compile_include('\luaescapestring{\unexpanded{#2}}')
}
\end{NodetreeEmbedView}
+ \directlua{nodetree.pop_options()}
}
% \end{macrocode}
% \end{macro}
@@ -468,10 +459,12 @@
% \begin{macro}{\NodetreeEmbedInput}
% \begin{macrocode}
\newcommand{\NodetreeEmbedInput}[2][]{
+ \directlua{nodetree.push_options()}
\setkeys{NTE}{#1}
\begin{NodetreeEmbedView}
- \input{#2.nttex}
+ \directlua{nodetree.input('#2.nttex')}
\end{NodetreeEmbedView}
+ \directlua{nodetree.pop_options()}
}
\let\nodetreeterminalemulator\NodetreeEmbedInput
% \end{macrocode}
diff --git a/macros/luatex/generic/nodetree/nodetree.ins b/macros/luatex/generic/nodetree/nodetree.ins
index eaee61309b..e1ded5bb1b 100644
--- a/macros/luatex/generic/nodetree/nodetree.ins
+++ b/macros/luatex/generic/nodetree/nodetree.ins
@@ -1,4 +1,4 @@
-% Copyright (C) 2016-2022 by Josef Friedrich <josef@friedrich.rocks>
+% Copyright (C) 2016-2023 by Josef Friedrich <josef@friedrich.rocks>
% ----------------------------------------------------------------------
% This work may be distributed and/or modified under the conditions of
% the LaTeX Project Public License, either version 1.3c of this license
@@ -21,7 +21,7 @@
This is a generated file.
-Copyright (C) 2016-2022 by Josef Friedrich <josef@friedrich.rocks>
+Copyright (C) 2016-2023 by Josef Friedrich <josef@friedrich.rocks>
----------------------------------------------------------------------
This work may be distributed and/or modified under the conditions of
the LaTeX Project Public License, either version 1.3c of this license
diff --git a/macros/luatex/generic/nodetree/nodetree.lua b/macros/luatex/generic/nodetree/nodetree.lua
index 728ffc7473..18a1f204c3 100644
--- a/macros/luatex/generic/nodetree/nodetree.lua
+++ b/macros/luatex/generic/nodetree/nodetree.lua
@@ -1,35 +1,36 @@
---- The nodetree package.
---
--- Nodetree uses [LDoc](https://github.com/stevedonovan/ldoc) for the
--- source code documentation. The supported tags are described on in
--- the [wiki](https://github.com/stevedonovan/LDoc/wiki).
---
--- Nodes in LuaTeX are connected. The nodetree view distinguishs
--- between the `list` and `field` connections.
---
--- * `list`: Nodes, which are double connected by `next` and
--- `previous` fields.
--- * `field`: Connections to nodes by other fields than `next` and
--- `previous` fields, e. g. `head`, `pre`.
--- @module nodetree
-
--- luacheck: globals node tex luatexbase lfs callback os unicode status modules
-
----@class Node
----@field next Node|nil # the next node in a list, or nil
----@field id number # the node’s type (id) number
----@field subtype number # the node subtype identifier
-
----@alias ColorName `black` | `red` | `green` | `yellow` | `blue` | `magenta` | `cyan` | `white`
----@alias ColorMode `bright` | `dim`
-
----@alias ConnectionType `list` | `field` # A literal
--- is a string, which can be either `list` or `field`.
----@alias ConnectionState `stop` | `continue` # A literal which can
--- be either `continue` or `stop`.
-
-if not modules then modules = { } end modules ['nodetree'] = {
- version = '2.2.1',
+--- This file (`nodetree.lua`) is part of the LuaTeX package
+--- 'nodetree'.
+---
+---`nodetree` uses the annotation system of the
+---[lua-language-server](https://github.com/LuaLS/lua-language-server/wiki/Annotations).
+---Install the [type definitions for LuaTeX](https://github.com/Josef-Friedrich/LuaTeX_Lua-API)
+---or the [Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=JosefFriedrich.luatex).
+---
+---The LDoc support is deprecated.
+---
+---Nodes in LuaTeX are connected. The nodetree view distinguishes
+---between *list* and *field* connections.
+---
+---* list: Nodes that are doubly connected by `next` and `previous`
+--- fields.
+---* field: Connections to nodes by other fields than `next` and
+--- `previous` fields, e.g., `head`, `pre`.
+---@module nodetree
+
+---luacheck: globals node lang tex luatexbase lfs
+---luacheck: globals callback os unicode status modules
+
+---
+---@alias ColorName 'black'|'red' |'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'reset'
+---@alias ColorMode 'bright'|'dim'|''
+---
+---@alias ConnectionType 'list'|'field' # A string literal,
+--- which can be either 'list' or 'field'.
+---@alias ConnectionState 'stop'|'continue' # A literal, which can
+--- be either `continue` or `stop`.
+
+if not modules then modules = {} end modules ['nodetree'] = {
+ version = '2.3.0',
comment = 'nodetree',
author = 'Josef Friedrich',
copyright = 'Josef Friedrich',
@@ -39,58 +40,71 @@ if not modules then modules = { } end modules ['nodetree'] = {
local direct = node.direct
local todirect = direct.todirect
local getchar = direct.getchar
---- Lua 5.1 does not have the utf8 library (Lua 5.1 is the default
--- version in LuajitTeX). LuaJitTeX does include the slnunicode library.
+---Lua 5.1 does not have the utf8 library (Lua 5.1 is the default
+---version in LuajitTeX). LuaJitTeX does include the slnunicode library.
local utf8 = utf8 or unicode.utf8
local utfchar = utf8.char
local properties = direct.get_properties_table()
---- A counter for the compiled TeX examples. Some TeX code snippets
--- a written into file, wrapped with some TeX boilerplate code.
--- This written files are compiled.
+---A counter for the compiled TeX examples. Some TeX code snippets
+---a written into files, wrapped with some TeX boilerplate code.
+---These written files are compiled later on.
local example_counter = 0
---- The default options
+---A flag to indicate that something has been emitted by nodetree.
+local have_output = false
+
+--- The default options.
local default_options = {
callback = 'post_linebreak_filter',
channel = 'term',
color = 'colored',
decimalplaces = 2,
unit = 'pt',
- verbosity = 1,
+ verbosity = 0,
+ firstline = 1,
+ lastline = -1,
}
---- The current options
--- They are changed very often.
+--- The current options.
local options = {}
for key, value in pairs(default_options) do
options[key] = value
end
---- File descriptor
+--- The previous options.
+---We need this for functions `push_options` and `pop_options` so that
+---the effects of the `\setkeys` commands in `nodetree-embed.sty`
+---(which directly manipulates the `options` table) stay local.
+local prev_options = {}
+local option_level = 0
+
+---File descriptor.
local output_file
---- The lua table named `tree_state` holds state values of the current
--- tree item.
---
--- `tree_state`:
---
--- * `1` (level):
--- * `list`: `continue`
--- * `field`: `stop`
--- * `2`:
--- * `list`: `continue`
--- * `field`: `stop`
--- @table
+--- The state values of the current tree item.
+---
+---`tree_state`:
+---
+---* `1` (level):
+--- * `list`: `continue`
+--- * `field`: `stop`
+---* `2`:
+--- * `list`: `continue`
+--- * `field`: `stop`
+---
+---...
local tree_state = {}
--- Format functions.
---
--- Low level template functions.
---
--- @section format
+---
+---Low-level template functions.
+---
+---@section format
local format = {
+ ---@function format.underscore
+ ---
---@param input string
---
---@return string
@@ -103,6 +117,8 @@ local format = {
end
end,
+ ---@function format.escape
+ ---
---@param input string
---
---@return string
@@ -115,6 +131,8 @@ local format = {
end
end,
+ ---@function format.function
+ ---
---@param input number
---
---@return number
@@ -123,7 +141,9 @@ local format = {
return math.floor(input * mult + 0.5) / mult
end,
- ---@param count? number # how many spaces should be output
+ ---@function format.whitespace
+ ---
+ ---@param count? number # How many spaces should be output.
---
---@return string
whitespace = function(count)
@@ -143,6 +163,8 @@ local format = {
return output
end,
+ ---@function format.color_code
+ ---
---@param code number
---
---@return string
@@ -150,6 +172,10 @@ local format = {
return string.char(27) .. '[' .. tostring(code) .. 'm'
end,
+ ---@function format.color_tex
+ ---
+ ---@param color string
+ ---@param mode? string
---
---@return string
color_tex = function(color, mode)
@@ -157,6 +183,7 @@ local format = {
return 'NTE' .. color .. mode
end,
+ ---@function format.node_begin
---
---@return string
node_begin = function()
@@ -167,6 +194,7 @@ local format = {
end
end,
+ ---@function format.node_end
---
---@return string
node_end = function()
@@ -177,7 +205,9 @@ local format = {
end
end,
- ---@param count? number # how many new lines should be output
+ ---@function format.new_line
+ ---
+ ---@param count? number # How many new lines should be output.
---
---@return string
new_line = function(count)
@@ -187,7 +217,7 @@ local format = {
end
local new_line
if options.channel == 'tex' then
- new_line = '\\par{}'
+ new_line = '\\par\n'
else
new_line = '\n'
end
@@ -198,6 +228,8 @@ local format = {
return output
end,
+ ---@function format.type_id
+ ---
---@param id number
---
---@return string
@@ -207,8 +239,8 @@ local format = {
}
--- Print the output to stdout or write it into a file (`output_file`).
--- New text is appended.
---
+---New text is appended.
+---
---@param text string # A text string.
local function nodetree_print(text)
if options.channel == 'log' or options.channel == 'tex' then
@@ -219,7 +251,8 @@ local function nodetree_print(text)
end
--- Template functions.
--- @section template
+---
+---@section template
local template = {
node_colors = {
@@ -275,8 +308,28 @@ local template = {
shape = {'yellow'},
},
- ---
- -- [SGR (Select Graphic Rendition) Parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters)
+ -- Field name abbreviations for verbosity level 0. A second field
+ -- limits the abbreviation to this node type.
+ --
+ -- Entry '' means to omit the key, printing only the value. Entry
+ -- '()' means the same, but the value gets printed in parentheses.
+ field_abbrevs = {
+ char = {''},
+ depth = {'dp'},
+ dir = {'()', 'dir'},
+ height = {'ht'},
+ kern = {''},
+ mark = {''},
+ penalty = {'', 'penalty'},
+ shrink = {'minus'},
+ stretch = {'plus'},
+ style = {''},
+ subtype = {'()'},
+ width = {'wd'},
+ },
+
+ --- [SGR (Select Graphic Rendition)
+ -- parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters).
--
-- __attributes__
--
@@ -317,10 +370,12 @@ local template = {
-- | oncyan | 46 |
-- | onwhite | 47 |
--
+ ---@function template.color
+ ---
---@param color ColorName # A color name.
---@param mode? ColorMode
- ---@param background? boolean # Colorize the background not the text.
- --
+ ---@param background? boolean # If set, colorize the background instead of the text.
+ ---
---@return string
color = function(color, mode, background)
if options.color ~= 'colored' then
@@ -360,12 +415,12 @@ local template = {
end,
--- Format the char field of a node. Try to find a textual representation that
- -- corresponds with the number stored into the `char` field.
+ -- corresponds with the number stored in the `char` field.
--
- -- LuaTeX’s `node.char` are not really characters, they are font glyph indices
+ -- LuaTeX’s `node.char` values are not really characters; they are font glyph indices
-- which sometimes happen to match valid Unicode characters. HarfBuzz shapers
- -- differentiates between glyph IDs and characters by adding to 0x120000 to
- -- glyph ID.
+ -- differentiate between glyph IDs and characters by adding to 0x120000 to
+ -- glyph IDs.
--
-- The code of this function is borrowed from the [function
-- `get_glyph_info(n)`](https://github.com/latex3/luaotfload/blob/4c09fe264c1644792d95182280be259449e7da02/src/luaotfload-harf-plug.lua#L1018-L1031)
@@ -392,34 +447,54 @@ local template = {
-- It should also noted that this mapping is not unique, the same glyph
-- can represent different characters in different context.
--
+ ---@function template.char
+ --
---@param head Node # The head node of a node list.
---
- ---@return string # A textual representation of the `char` number. In verbosity level 2 or great suffixed with `[char number]`
+ ---@return string # A textual representation of the `char` number.
char = function(head)
- -- See Issues #6 and #9
- local node_id = todirect(head) -- Convert to node id
+ local node_id = todirect(head) -- Convert to node id.
local props = properties[node_id]
local info = props and props.glyph_info
local textual
local character_index = getchar(node_id)
+
if info then
textual = info
elseif character_index == 0 then
textual = '^^@'
elseif character_index <= 31 or (character_index >= 127 and character_index <= 159) then
- -- The C0 range [c-zero] is the characters from U+0000 to U+001F
- -- (decimal 0-31) and U+007F (decimal 127), the C1 range is the
+ -- The C0 range [c-zero] contains characters from U+0000 to U+001F
+ -- (decimal 0-31) and U+007F (decimal 127), the C1 range covers
-- characters from U+0080 to U+009F (decimal 128-159).
textual = '???'
- elseif character_index < 0x110000 then
+ elseif character_index ~= nil and character_index < 0x110000 then
textual = utfchar(character_index)
else
- textual = string.format("^^^^^^%06X", character_index)
+ textual = string.format('^^^^^^%06X', character_index)
+ end
+
+ if options.verbosity == 0 then
+ if textual == '???' then
+ return character_index
+ else
+ return "'" .. textual .. "'"
+ end
+ elseif options.verbosity <= 2 then
+ return character_index .. " ('" .. textual .. "')"
+ else
+ return character_index
+ .. ' ('
+ .. string.format('0x%x', character_index)
+ .. ", '"
+ .. textual
+ .. "')"
end
- return character_index .. ' (' .. string.format('0x%x', character_index) .. ', \''.. textual .. '\')'
end,
- ---@param length? `long`
+ ---@function template.line
+ ---
+ ---@param length string # If `long`, emit a longer line.
---
---@return string
line = function(length)
@@ -432,6 +507,8 @@ local template = {
return output .. format.new_line()
end,
+ ---@function template.branch
+ ---
---@param connection_type ConnectionType
---@param connection_state ConnectionState
---@param last boolean
@@ -465,7 +542,7 @@ local template = {
---@param number number
---@param order number
---@param field string
---
+---
---@return string
function template.fill(number, order, field)
local output
@@ -489,12 +566,12 @@ function template.fill(number, order, field)
end
--- Colorize a text string.
---
----@param text string A text string.
----@param color ColorName A color name.
----@param mode ColorMode
----@param background? boolean # Colorize the background not the text.
---
+---
+---@param text string # A text string.
+---@param color ColorName # A color name.
+---@param mode? ColorMode
+---@param background? boolean # If set, colorize the background instead of the text.
+---
---@return string
function template.colored_string(text, color, mode, background)
if options.channel == 'tex' then
@@ -512,12 +589,12 @@ function template.colored_string(text, color, mode, background)
end
--- Format a scaled point input value into dimension string (`12pt`,
--- `1cm`)
---
+--- `1cm`)
+---
---@param input number
---
+---
---@return string
-function template.length (input)
+function template.length(input)
local i = tonumber(input)
if i ~= nil then
input = i / tex.sp('1' .. options.unit)
@@ -530,16 +607,17 @@ function template.length (input)
end
--- Get all data from a table including metatables.
---
--- Properties will reside in a metatable, if nodes were copied using an
--- operation like box copy: (\copy). The LuaTeX manual states this: “If
--- the second argument of `set_properties_mode` is true, then a
--- metatable approach is chosen: the copy gets its own table with the
--- original table as metatable.”
---
--- Source: https://stackoverflow.com/a/5639667 Works if __index returns
--- table, which it should as per luatex manual
---
+---
+---Properties will reside in a metatable if nodes were copied using an
+---operation like box copy: (\copy). The LuaTeX manual states this: “If
+---the second argument of `set_properties_mode` is true, then a
+---metatable approach is chosen: the copy gets its own table with the
+---original table as metatable.”
+---
+---Source: [StackOverflow](https://stackoverflow.com/a/5639667) – this
+---works if `__index` returns a table, which it should as per LuaTeX
+---manual.
+---
---@param data table # A Lua table.
---@param previous_data? table # The data of a Lua table of a previous recursive call.
---
@@ -553,7 +631,7 @@ local function get_all_table_data(data, previous_data)
output[key] = output[key] or value
end
- -- Get table’s metatable, or exit if not existing
+ -- Get table’s metatable, or exit if not existing.
local metatable = getmetatable(data)
if type(metatable) ~= 'table' then
return output
@@ -565,14 +643,14 @@ local function get_all_table_data(data, previous_data)
return output
end
- -- Include the data from index into data, recursively, and return.
+ -- Include the data from index into data recursively and return.
return get_all_table_data(index, output)
end
--- Convert a Lua table into a format string.
---
----@param table table A table to generate a inline view of.
---
+---
+---@param table table # A table to generate an inline view of.
+---
---@return string
function template.table_inline(table)
local tex_escape = ''
@@ -598,23 +676,51 @@ function template.table_inline(table)
end
end
---- Format a key value pair (`key: value, `).
---
+--- Format a key-value pair (`key: value, `).
+---
---@param key string # A key.
---@param value string|number # A value.
+---@param typ? string # A node type. Had to be named typ to avoid conflict with the type() function.
---@param color? ColorName # A color name.
---
+---
---@return string
-function template.key_value(key, value, color)
+function template.key_value(key, value, typ, color)
if type(color) ~= 'string' then
color = 'yellow'
end
- if options.channel == 'tex' then
- key = format.underscore(key)
+ key = format.underscore(key)
+
+ local output = ''
+ local abbrev = nil
+ local separator = ':'
+
+ if options.verbosity == 0 then
+ if template.field_abbrevs[key] then
+ local only_this_type = template.field_abbrevs[key][2]
+ if not only_this_type or not typ or only_this_type == typ then
+ abbrev = template.field_abbrevs[key][1]
+ end
+ end
+ separator = ''
+ end
+
+ if abbrev == nil then
+ output = template.colored_string(key .. separator, color)
+ elseif abbrev ~= '' and abbrev ~= '()' then
+ output = template.colored_string(abbrev, color)
end
- local output = template.colored_string(key .. ':', color)
+
if value then
- output = output .. ' ' .. value .. ', '
+ if abbrev == '()' then
+ -- Printing '(unused)' is confusing.
+ if value ~= 'unused' then
+ output = output .. '(' .. value .. ') '
+ end
+ elseif abbrev == '' then
+ output = output .. value .. ', '
+ else
+ output = output .. ' ' .. value .. ', '
+ end
end
return output
end
@@ -625,30 +731,30 @@ end
---@return string
function template.type(type, id)
local output
- if options.channel == 'tex' then
- output = format.underscore(type)
- else
- output = type
- end
+ output = format.underscore(type)
output = string.upper(output)
if options.verbosity > 1 then
output = output .. format.type_id(id)
end
return template.colored_string(
- output .. format.whitespace(),
+ output,
template.node_colors[type][1],
template.node_colors[type][2]
)
end
---@param callback_name string
----@param variables? table
----
----@return string
-function template.callback(callback_name, variables)
+---@param variables table|nil
+---@param where 'before'|'after' # `'before'` or `'after'`
+function template.callback(callback_name, variables, where)
+ if options.channel == 'term' or have_output == true then
+ nodetree_print(format.new_line(2))
+ end
+
+ have_output = true
+
nodetree_print(
- format.new_line(2) ..
- 'Callback: ' ..
+ where .. ' callback ' ..
template.colored_string(format.underscore(callback_name), 'red', '', true) ..
format.new_line()
)
@@ -659,7 +765,7 @@ function template.callback(callback_name, variables)
'- ' ..
format.underscore(name) ..
': ' ..
- tostring(value) ..
+ format.underscore(tostring(value)) ..
format.new_line()
)
end
@@ -668,6 +774,8 @@ function template.callback(callback_name, variables)
nodetree_print(template.line('long'))
end
+--- Format the branching tree for one output line.
+---
---@param level number
---@param connection_type ConnectionType
---
@@ -678,7 +786,7 @@ function template.branches(level, connection_type)
output = output .. template.branch('list', tree_state[i]['list'], false)
output = output .. template.branch('field', tree_state[i]['field'], false)
end
--- Format the last branches
+---Format the last branches
if connection_type == 'list' then
output = output .. template.branch('list', tree_state[level]['list'], true)
else
@@ -688,20 +796,21 @@ function template.branches(level, connection_type)
return output
end
---- Extend the node library
--- @section node_extended
+--- Node library extensions.
+---
+---@section node_extended
local node_extended = {}
--- Get the ID of a node.
---
--- We have to convert the node into a string and than have to extract
--- the ID from this string using a regular expression. If you convert a
--- node into a string it looks like: `<node nil < 172 > nil :
--- hlist 2>`.
---
+---
+---We have to convert the node into a string and then to extract
+---the ID from this string using a regular expression. If you convert a
+---node into a string it looks like: `<node nil < 172 > nil :
+---hlist 2>`.
+---
---@param n Node # A node.
---
+---
---@return string
function node_extended.node_id(n)
local result = string.gsub(tostring(n), '^<node%s+%S+%s+<%s+(%d+).*', '%1')
@@ -709,49 +818,46 @@ function node_extended.node_id(n)
end
--- A table of all node subtype names.
---
--- __Nodes without subtypes:__
---
--- * `ins` (3)
--- * `mark` (4)
--- * `whatsit` (8)
--- * `local_par` (9)
--- * `dir` (10)
--- * `penalty` (14)
--- * `unset` (15)
--- * `style` (16)
--- * `choice` (17)
--- * `fraction` (20)
--- * `math_char` (23)
--- * `sub_box` (24)
--- * `sub_mlist` (25)
--- * `math_text_char` (26)
--- * `delim` (27)
--- * `margin_kern` (28)
--- * `align_record` (30)
--- * `pseudo_file` (31)
--- * `pseudo_line` (32)
--- * `page_insert` (33)
--- * `split_insert` (34)
--- * `expr_stack` (35)
--- * `nested_list` (36)
--- * `span` (37)
--- * `attribute` (38)
--- * `glue_spec` (39)
--- * `attribute_list` (40)
--- * `temp` (41)
--- * `align_stack` (42)
--- * `movement_stack` (43)
--- * `if_stack` (44)
--- * `unhyphenated` (45)
--- * `hyphenated` (46)
--- * `delta` (47)
--- * `passive` (48)
--- * `shape` (49)
---
+---
+---__Nodes without subtypes:__
+---
+---* `ins` (3)
+---* `local_par` (9)
+---* `penalty` (14)
+---* `unset` (15)
+---* `style` (16)
+---* `choice` (17)
+---* `fraction` (20)
+---* `math_char` (23)
+---* `sub_box` (24)
+---* `sub_mlist` (25)
+---* `math_text_char` (26)
+---* `delim` (27)
+---* `margin_kern` (28)
+---* `align_record` (30)
+---* `pseudo_file` (31)
+---* `pseudo_line` (32)
+---* `page_insert` (33)
+---* `split_insert` (34)
+---* `expr_stack` (35)
+---* `nested_list` (36)
+---* `span` (37)
+---* `attribute` (38)
+---* `glue_spec` (39)
+---* `attribute_list` (40)
+---* `temp` (41)
+---* `align_stack` (42)
+---* `movement_stack` (43)
+---* `if_stack` (44)
+---* `unhyphenated` (45)
+---* `hyphenated` (46)
+---* `delta` (47)
+---* `passive` (48)
+---* `shape` (49)
+---
---@return table
-local function get_node_subtypes ()
- local subtypes = {
+local function get_node_subtypes()
+ local subtypes = {
-- hlist (0)
hlist = {
[0] = 'unknown',
@@ -803,6 +909,11 @@ local function get_node_subtypes ()
[8] = 'radical',
[9] = 'outline',
},
+ -- mark (4)
+ -- The subtype is not used.
+ mark = {
+ [0] = 'unused',
+ },
-- adjust (5)
adjust = {
[0] = 'normal',
@@ -816,7 +927,7 @@ local function get_node_subtypes ()
[3] = 'word',
},
-- disc (7)
- disc = {
+ disc = {
[0] = 'discretionary',
[1] = 'explicit',
[2] = 'automatic',
@@ -824,6 +935,13 @@ local function get_node_subtypes ()
[4] = 'first',
[5] = 'second',
},
+ -- dir (10)
+ -- This is an internal detail, see luatex source code file
+ -- `texnodes.h`.
+ -- dir = {
+ -- [0] = 'normal_dir',
+ -- [1] = 'cancel_dir',
+ -- },
-- math (11)
math = {
[0] = 'beginmath',
@@ -923,8 +1041,8 @@ local function get_node_subtypes ()
[1] = 'right',
},
-- glyph (29)
- -- the subtype for this node is a bit field, not an enumeration;
- -- bit 0 gets handled separately
+ -- The subtype for this node is a bit field, not an enumeration;
+ -- bit 0 gets handled separately.
glyph = {
[1] = 'ligature',
[2] = 'ghost',
@@ -945,7 +1063,7 @@ function node_extended.subtype(n)
local output = ''
if subtypes[typ] then
if typ == 'glyph' then
- -- only handle the lowest five bits
+ -- Only handle the lowest five bits.
if n.subtype & 1 == 0 then
output = output .. 'glyph'
else
@@ -975,38 +1093,46 @@ function node_extended.subtype(n)
end
end
---- Build the node tree.
--- @section tree
+--- Node tree building functions.
+---
+---@section tree
local tree = {}
---
---@param head Node # The head node of a node list.
---@param field string
---
+---
---@return string
function tree.format_field(head, field)
local output
+ local typ = node.type(head.id)
- -- subtypes with IDs 0 are were not printed, see #12
- if head[field] ~= nil and field == "subtype" then
- return template.key_value(field, format.underscore(node_extended.subtype(head)))
+ -- Print subtypes also for nodes with ID=0. However, suppress the
+ -- internal 'subtype' field for 'dir' nodes.
+ if field == 'subtype' then
+ if typ == 'dir' then
+ return ''
+ elseif head[field] ~= nil then
+ return template.key_value(field,
+ format.underscore(node_extended.subtype(head)))
+ end
end
- -- Character "0" should be printed in a tree, because in TeX fonts the
- -- 0 slot usually has a symbol.
- if head[field] == nil or (head[field] == 0 and field ~= "char") then
+ -- Character 0 should be printed in a tree because the corresponding slot
+ -- zero in a TeX font usually contains a symbol.
+ if head[field] == nil or (head[field] == 0 and field ~= 'char') then
return ''
end
if options.verbosity < 2 and
-- glyph
- field == 'font' or
field == 'left' or
field == 'right' or
field == 'uchyph' or
-- hlist
- field == 'dir' or
+ -- Don't drop the 'dir' field of the 'dir' node.
+ (field == 'dir' and typ ~= 'dir') or
field == 'glue_order' or
field == 'glue_sign' or
field == 'glue_set' or
@@ -1016,8 +1142,7 @@ function tree.format_field(head, field)
elseif options.verbosity < 3 and
field == 'prev' or
field == 'next' or
- field == 'id'
- then
+ field == 'id' then
return ''
end
@@ -1037,37 +1162,50 @@ function tree.format_field(head, field)
elseif field == 'stretch' or field == 'shrink' then
output = template.fill(head[field], head[field .. '_order'], field)
else
- output = tostring(head[field])
+ -- Surround strings with single quotes except values of fields
+ -- that get potentially abbreviated (and thus don't really need
+ -- quotes).
+ if type(head[field]) == 'string' and not template.field_abbrevs[field] then
+ output = template.colored_string("'", 'yellow') ..
+ head[field] ..
+ template.colored_string("'", 'yellow')
+ elseif type(head[field]) == 'table' then
+ output = '<table>'
+ else
+ output = tostring(head[field])
+ end
end
- return template.key_value(field, output)
+ return template.key_value(field, output, node.type(head.id))
end
---
--- Attributes are key/value number pairs. They are printed as an inline
--- list. The attribute `0` with the value `0` is skipped because this
--- attribute is in every node by default.
---
+---Attributes are key-value number pairs. They are printed as an inline
+---list. The attribute `0` with the value `0` is skipped because this
+---attribute is in every node by default.
+---
---@param head Node # The head node of a node list.
---
+---
---@return string
function tree.format_attributes(head)
if not head then
return ''
end
+ local space = ''
local output = ''
- local attr = head.next
+ local attr = head.next --[[@as AttributeNode]]
while attr do
if attr.number ~= 0 or (attr.number == 0 and attr.value ~= 0) then
- output = output .. tostring(attr.number) .. '=' .. tostring(attr.value) .. ' '
+ output = output .. space .. tostring(attr.number) .. '=' .. tostring(attr.value)
+ space = ' '
end
- attr = attr.next
+ attr = attr.next --[[@as AttributeNode]]
end
return output
end
---
----@param level number # `level` is a integer beginning with 1.
+---@param level number # `level` is an integer beginning with 1.
---@param connection_type ConnectionType
---@param connection_state ConnectionState
function tree.set_state(level, connection_type, connection_state)
@@ -1079,7 +1217,7 @@ end
---
---@param fields table
----@param level number
+---@param level number # The current recursion level.
function tree.analyze_fields(fields, level)
local max = 0
local connection_state
@@ -1108,10 +1246,11 @@ end
---
---@param head Node # The head node of a node list.
----@param level number
+---@param level number # The current recursion level.
function tree.analyze_node(head, level)
local connection_state
local output
+ local need_whitespace = true
if head.next then
connection_state = 'continue'
else
@@ -1121,16 +1260,20 @@ function tree.analyze_node(head, level)
output = template.branches(level, 'list')
.. template.type(node.type(head.id), head.id)
if options.verbosity > 1 then
- output = output .. template.key_value('no', node_extended.node_id(head))
+ output = output ..
+ format.whitespace() ..
+ template.key_value('no', node_extended.node_id(head))
+ need_whitespace = false
end
- -- We store the attributes output to append it to the field list.
+ -- We store the attributes output so that we can append it to the field
+ -- list later on.
local attributes
-- We store fields which are nodes for later treatment.
local fields = {}
- -- Inline fields, for example: char: 'm', width: 25pt, height: 13.33pt,
+ -- Inline fields, for example: char: 'm', width: 25pt, height: 13.33pt.
local output_fields = ''
for _, field_name in pairs(node.fields(head.id, head.subtype)) do
if field_name == 'attr' then
@@ -1143,12 +1286,19 @@ function tree.analyze_node(head, level)
end
end
if output_fields ~= '' then
+ if need_whitespace == true then
+ output = output .. format.whitespace()
+ need_whitespace = false
+ end
output = output .. output_fields
end
- -- Append the attributes output if available
- if attributes ~= '' then
- output = output .. template.key_value('attr', attributes, 'blue')
+ -- Append the attributes output if available.
+ if attributes and attributes ~= '' then
+ if need_whitespace == true then
+ output = output .. format.whitespace()
+ end
+ output = output .. template.key_value('attr', attributes, nil, 'blue')
end
output = output:gsub(', $', '')
@@ -1162,11 +1312,19 @@ function tree.analyze_node(head, level)
local property = node.getproperty(head)
if property then
+ local props
+ if options.verbosity == 0 then
+ props = 'props'
+ else
+ props = 'properties:'
+ end
+
+ -- Print attributes in a separate line.
nodetree_print(
format.node_begin() ..
template.branches(level, 'field') ..
' ' ..
- template.colored_string('properties:', 'blue') .. ' ' ..
+ template.colored_string(props, 'blue') .. ' ' ..
template.table_inline(property) ..
format.node_end() ..
format.new_line()
@@ -1176,9 +1334,10 @@ function tree.analyze_node(head, level)
tree.analyze_fields(fields, level)
end
+--- Recurse over the current node list.
---
---@param head Node # The head node of a node list.
----@param level number
+---@param level number # The current recursion level.
function tree.analyze_list(head, level)
while head do
tree.analyze_node(head, level)
@@ -1186,95 +1345,248 @@ function tree.analyze_list(head, level)
end
end
+--- The top-level internal entry point.
---
---@param head Node # The head node of a node list.
function tree.analyze_callback(head)
tree.analyze_list(head, 1)
- nodetree_print(template.line('short') .. format.new_line())
+ nodetree_print(template.line('short'))
end
---- Callback wrapper.
--- @section callbacks
+local orig_callbacks = {}
+local orig_descriptions = {}
-local callbacks = {
+local print_positions = {}
+local callback_has_default_action = {
+ hyphenate = true,
+ ligaturing = true,
+ kerning = true,
+ mlist_to_hlist = true
+}
+
+--- Callback wrappers.
+---
+---Nodetree uses luatexbase's functions to manage callbacks if
+---available. Otherwise a simplistic approach is taken by prepending
+---or appending nodetree's diagnostic callbacks to the existing ones
+---(and also removing them again if requested).
+---
+---Each function in the `callback_wrappers` table consists of three
+---parts:
+---
+---* before-callback inspection
+---* original callback or default function call
+---* after-callback inspection
+---
+---The actual callback functions are stored in the `callbacks` table.
+---
+---@section callbacks
+local callback_wrappers = {
+ ---@function callbacks.contribute_filter
---
---@param extrainfo string
- ---
- ---@return boolean
- contribute_filter = function(extrainfo)
- template.callback('contribute_filter', {extrainfo = extrainfo})
- return true
+ ---@param where string
+ contribute_filter = function(extrainfo, where)
+ local cb = 'contribute_filter'
+ local before, after = template.get_print_position(where)
+
+ if before then
+ template.callback(cb, {extrainfo = extrainfo}, before)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ orig_callbacks[cb](extrainfo)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {extrainfo = extrainfo}, after)
+ end
end,
+ ---@function callbacks.buildpage_filter
---
---@param extrainfo string
- ---
- ---@return boolean
- buildpage_filter = function(extrainfo)
- template.callback('buildpage_filter', {extrainfo = extrainfo})
- return true
+ ---@param where string
+ buildpage_filter = function(extrainfo, where)
+ local cb = 'buildpage_filter'
+ local before, after = template.get_print_position(where)
+
+ if before then
+ template.callback(cb, {extrainfo = extrainfo}, before)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ orig_callbacks[cb](extrainfo)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {extrainfo = extrainfo}, after)
+ end
end,
+ ---@function callbacks.build_page_insert
---
---@param n string
---@param i string
---
---@return number
build_page_insert = function(n, i)
- template.callback('build_page_insert', {n = n, i = i})
- return 0
+ local cb = 'build_page_insert'
+ local before, after = template.get_print_position(cb)
+ local retval = 0
+
+ if before then
+ template.callback(cb, {n = n, i = i}, before)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ retval = orig_callbacks[cb](n, i)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {n = n, i = i}, after)
+ end
+
+ return retval
end,
+ ---@function callbacks.pre_linebreak_filter
---
---@param head Node # The head node of a node list.
---@param groupcode string
+ ---@param where string
---
---@return boolean
- pre_linebreak_filter = function(head, groupcode)
- template.callback('pre_linebreak_filter', {groupcode = groupcode})
- tree.analyze_callback(head)
- return true
+ pre_linebreak_filter = function(head, groupcode, where)
+ local cb = 'pre_linebreak_filter'
+ local before, after = template.get_print_position(where)
+ local retval = true
+
+ if before then
+ template.callback(cb, {groupcode = groupcode}, before)
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ retval = orig_callbacks[cb](head, groupcode)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {groupcode = groupcode}, after)
+ tree.analyze_callback(head)
+ end
+
+ return retval
end,
+ ---@function callbacks.linebreak_filter
---
---@param head Node # The head node of a node list.
---@param is_display boolean
---
---@return boolean
linebreak_filter = function(head, is_display)
- template.callback('linebreak_filter', {is_display = is_display})
- tree.analyze_callback(head)
- return true
+ local cb = 'linebreak_filter'
+ local before, after = template.get_print_position(cb)
+ local retval = true
+
+ if before then
+ template.callback(cb, {is_display = is_display}, before)
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ retval = orig_callbacks[cb](head, is_display)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {is_display = is_display}, after)
+ tree.analyze_callback(head)
+ end
+
+ return retval
end,
+ ---@function callbacks.append_to_vlist_filter
---
---@param box Node
---@param locationcode string
---@param prevdepth number
---@param mirrored boolean
+ ---
+ ---@return Node
+ ---@return number
append_to_vlist_filter = function(box, locationcode, prevdepth, mirrored)
- local variables = {
- locationcode = locationcode,
- prevdepth = prevdepth,
- mirrored = mirrored,
- }
- template.callback('append_to_vlist_filter', variables)
- tree.analyze_callback(box)
- return box
+ local cb = 'append_to_vlist_filter'
+ local before, after = template.get_print_position(cb)
+
+ if before then
+ template.callback(cb, {locationcode = locationcode,
+ prevdepth = prevdepth,
+ mirrored = mirrored}, before)
+ tree.analyze_callback(box)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ box, prevdepth = orig_callbacks[cb](box, locationcode,
+ prevdepth, mirrored)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {locationcode = locationcode,
+ prevdepth = prevdepth,
+ mirrored = mirrored}, after)
+ tree.analyze_callback(box)
+ end
+
+ return box, prevdepth
end,
+ ---@function callbacks.post_linebreak_filter
---
---@param head Node # The head node of a node list.
---@param groupcode string
+ ---@param where string
---
---@return boolean
- post_linebreak_filter = function(head, groupcode)
- template.callback('post_linebreak_filter', {groupcode = groupcode})
- tree.analyze_callback(head)
- return true
+ post_linebreak_filter = function(head, groupcode, where)
+ local cb = 'post_linebreak_filter'
+ local before, after = template.get_print_position(where)
+ local retval = true
+
+ if before then
+ template.callback(cb, {groupcode = groupcode}, before)
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ retval = orig_callbacks[cb](head, groupcode)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {groupcode = groupcode}, after)
+ tree.analyze_callback(head)
+ end
+
+ return retval
end,
+ ---@function callbacks.hpack_filter
---
---@param head Node # The head node of a node list.
---@param groupcode string
@@ -1282,21 +1594,45 @@ local callbacks = {
---@param packtype string
---@param direction string
---@param attributelist Node
+ ---@param where string
---
---@return boolean
- hpack_filter = function(head, groupcode, size, packtype, direction, attributelist)
- local variables = {
- groupcode = groupcode,
- size = size,
- packtype = packtype,
- direction = direction,
- attributelist = attributelist,
- }
- template.callback('hpack_filter', variables)
- tree.analyze_callback(head)
- return true
+ hpack_filter = function(head, groupcode, size, packtype,
+ direction, attributelist, where)
+ local cb = 'hpack_filter'
+ local before, after = template.get_print_position(where)
+ local retval = true
+
+ if before then
+ template.callback(cb, {groupcode = groupcode,
+ size = size,
+ packtype = packtype,
+ direction = direction,
+ attributelist = attributelist}, before)
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ retval = orig_callbacks[cb](head, groupcode, size,
+ packtype, direction,
+ attributelist)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {groupcode = groupcode,
+ size = size,
+ packtype = packtype,
+ direction = direction,
+ attributelist = attributelist}, after)
+ tree.analyze_callback(head)
+ end
+
+ return retval
end,
+ ---@function callbacks.vpack_filter
---
---@param head Node # The head node of a node list.
---@param groupcode string
@@ -1305,39 +1641,88 @@ local callbacks = {
---@param maxdepth number
---@param direction string
---@param attributelist Node
+ ---@param where string
---
---@return boolean
- vpack_filter = function(head, groupcode, size, packtype, maxdepth, direction, attributelist)
- local variables = {
- groupcode = groupcode,
- size = size,
- packtype = packtype,
- maxdepth = template.length(maxdepth),
- direction = direction,
- attributelist = attributelist,
- }
- template.callback('vpack_filter', variables)
- tree.analyze_callback(head)
- return true
+ vpack_filter = function(head, groupcode, size, packtype,
+ maxdepth, direction, attributelist, where)
+ local cb = 'vpack_filter'
+ local before, after = template.get_print_position(where)
+ local retval = true
+
+ if before then
+ template.callback(cb, {groupcode = groupcode,
+ size = size,
+ packtype = packtype,
+ maxdepth = template.length(maxdepth),
+ direction = direction,
+ attributelist = attributelist}, before)
+ tree.analyze_callback(head)
+ tree.analyze_callback(attributelist)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ retval = orig_callbacks[cb](head, groupcode, size, packtype,
+ maxdepth, direction,
+ attributelist)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {groupcode = groupcode,
+ size = size,
+ packtype = packtype,
+ maxdepth = template.length(maxdepth),
+ direction = direction,
+ attributelist = attributelist}, after)
+ tree.analyze_callback(head)
+ tree.analyze_callback(attributelist)
+ end
+
+ return retval
end,
+ ---@function callbacks.hpack_quality
---
---@param incident string
---@param detail number
---@param head Node # The head node of a node list.
---@param first number
---@param last number
+ ---
+ ---@return Node
hpack_quality = function(incident, detail, head, first, last)
- local variables = {
- incident = incident,
- detail = detail,
- first = first,
- last = last,
- }
- template.callback('hpack_quality', variables)
- tree.analyze_callback(head)
+ local cb = 'hpack_quality'
+ local before, after = template.get_print_position(cb)
+ local retval = nil
+
+ if before then
+ template.callback(cb, {incident = incident,
+ detail = detail,
+ first = first,
+ last = last}, before)
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ retval = orig_callbacks[cb](incident, detail, head, first, last)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {incident = incident,
+ detail = detail,
+ first = first,
+ last = last}, after)
+ tree.analyze_callback(head)
+ end
+
+ return retval
end,
+ ---@function callbacks.vpack_quality
---
---@param incident string
---@param detail number
@@ -1345,32 +1730,59 @@ local callbacks = {
---@param first number
---@param last number
vpack_quality = function(incident, detail, head, first, last)
- local variables = {
- incident = incident,
- detail = detail,
- first = first,
- last = last,
- }
- template.callback('vpack_quality', variables)
- tree.analyze_callback(head)
+ local cb = 'vpack_quality'
+ local before, after = template.get_print_position(cb)
+
+ if before then
+ template.callback(cb, {incident = incident,
+ detail = detail,
+ first = first,
+ last = last}, before)
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ orig_callbacks[cb](incident, detail, head, first, last)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {incident = incident,
+ detail = detail,
+ first = first,
+ last = last}, after)
+ tree.analyze_callback(head)
+ end
end,
+ ---@function callbacks.process_rule
---
---@param head Node # The head node of a node list.
---@param width number
---@param height number
- ---
- ---@return boolean
process_rule = function(head, width, height)
- local variables = {
- width = width,
- height = height,
- }
- template.callback('process_rule', variables)
- tree.analyze_callback(head)
- return true
+ local cb = 'process_rule'
+ local before, after = template.get_print_position(cb)
+
+ if before then
+ template.callback(cb, {width = width, height = height}, before)
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ orig_callbacks[cb](head, width, height)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {width = width, height = height}, after)
+ tree.analyze_callback(head)
+ end
end,
+ ---@function callbacks.pre_output_filter
---
---@param head Node # The head node of a node list.
---@param groupcode string
@@ -1378,98 +1790,437 @@ local callbacks = {
---@param packtype string
---@param maxdepth number
---@param direction string
+ ---@param where string
---
---@return boolean
- pre_output_filter = function(head, groupcode, size, packtype, maxdepth, direction)
- local variables = {
- groupcode = groupcode,
- size = size,
- packtype = packtype,
- maxdepth = maxdepth,
- direction = direction,
- }
- template.callback('pre_output_filter', variables)
- tree.analyze_callback(head)
- return true
+ pre_output_filter = function(head, groupcode, size, packtype,
+ maxdepth, direction, where)
+ local cb = 'pre_output_filter'
+ local before, after = template.get_print_position(where)
+ local retval = true
+
+ if before then
+ template.callback(cb, {groupcode = groupcode,
+ size = size,
+ packtype = packtype,
+ maxdepth = maxdepth,
+ direction = direction}, before)
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ retval = orig_callbacks[cb](head, groupcode, size,
+ packtype, maxdepth,
+ direction)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {groupcode = groupcode,
+ size = size,
+ packtype = packtype,
+ maxdepth = maxdepth,
+ direction = direction}, after)
+ tree.analyze_callback(head)
+ end
+
+ return retval
end,
+ ---@function callbacks.hyphenate
---
---@param head Node # The head node of a node list.
---@param tail Node
- hyphenate = function(head, tail)
- template.callback('hyphenate')
- nodetree_print('head:' .. format.new_line())
- tree.analyze_callback(head)
- nodetree_print('tail:' .. format.new_line())
- tree.analyze_callback(tail)
+ ---@param where string
+ hyphenate = function(head, tail, where)
+ local cb = 'hyphenate'
+ local before, after = template.get_print_position(where)
+
+ if before then
+ template.callback(cb, nil, before)
+ nodetree_print('head:' .. format.new_line())
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ orig_callbacks[cb](head, tail)
+ end
+ else
+ template.no_callback(cb, true)
+ lang.hyphenate(head, tail)
+ end
+ if after then
+ template.callback(cb, nil, after)
+ nodetree_print('head:' .. format.new_line())
+ tree.analyze_callback(head)
+ end
end,
+ ---@function callbacks.ligaturing
---
---@param head Node # The head node of a node list.
---@param tail Node
- ligaturing = function(head, tail)
- template.callback('ligaturing')
- nodetree_print('head:' .. format.new_line())
- tree.analyze_callback(head)
- nodetree_print('tail:' .. format.new_line())
- tree.analyze_callback(tail)
+ ---@param where string
+ ligaturing = function(head, tail, where)
+ local cb = 'ligaturing'
+ local before, after = template.get_print_position(where)
+
+ if before then
+ template.callback(cb, nil, before)
+ nodetree_print('head:' .. format.new_line())
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ orig_callbacks[cb](head, tail)
+ end
+ else
+ template.no_callback(cb, true)
+ node.ligaturing(head, tail)
+ end
+ if after then
+ template.callback(cb, nil, after)
+ nodetree_print('head:' .. format.new_line())
+ tree.analyze_callback(head)
+ end
end,
+ ---@function callbacks.kerning
---
---@param head Node # The head node of a node list.
---@param tail Node
- kerning = function(head, tail)
- template.callback('kerning')
- nodetree_print('head:' .. format.new_line())
- tree.analyze_callback(head)
- nodetree_print('tail:' .. format.new_line())
- tree.analyze_callback(tail)
+ ---@param where string
+ kerning = function(head, tail, where)
+ local cb = 'kerning'
+ local before, after = template.get_print_position(where)
+
+ if before then
+ template.callback(cb, nil, before)
+ nodetree_print('head:' .. format.new_line())
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ orig_callbacks[cb](head, tail)
+ end
+ else
+ template.no_callback(cb, true)
+ node.kerning(head, tail)
+ end
+ if after then
+ template.callback(cb, nil, after)
+ nodetree_print('head:' .. format.new_line())
+ tree.analyze_callback(head)
+ end
end,
+ ---@function callbacks.insert_local_par
---
---@param local_par Node
---@param location string
- ---
- ---@return boolean
- insert_local_par = function(local_par, location)
- template.callback('insert_local_par', {location = location})
- tree.analyze_callback(local_par)
- return true
+ ---@param where string
+ insert_local_par = function(local_par, location, where)
+ local cb = 'insert_local_par'
+ local before, after = template.get_print_position(where)
+
+ if before then
+ template.callback(cb, {location = location}, before)
+ tree.analyze_callback(local_par)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ orig_callbacks[cb](local_par, location)
+ end
+ else
+ template.no_callback(cb)
+ end
+ if after then
+ template.callback(cb, {location = location}, after)
+ tree.analyze_callback(local_par)
+ end
end,
+ ---@function callbacks.mlist_to_hlist
---
---@param head Node # The head node of a node list.
---@param display_type string
---@param need_penalties boolean
+ ---
+ ---@return Node
mlist_to_hlist = function(head, display_type, need_penalties)
- local variables = {
- display_type = display_type,
- need_penalties = need_penalties,
- }
- template.callback('mlist_to_hlist', variables)
- tree.analyze_callback(head)
- return node.mlist_to_hlist(head, display_type, need_penalties)
+ local cb = 'mlist_to_hlist'
+ local before, after = template.get_print_position(cb)
+ local retval
+
+ if before then
+ template.callback(cb, {display_type = display_type,
+ need_penalties = need_penalties}, before)
+ tree.analyze_callback(head)
+ end
+ if orig_callbacks[cb] then
+ if orig_callbacks[cb] ~= '' then
+ retval = orig_callbacks[cb](head, display_type, need_penalties)
+ end
+ else
+ template.no_callback(cb, true)
+ retval = node.mlist_to_hlist(head, display_type, need_penalties)
+ end
+ if after then
+ template.callback(cb, {display_type = display_type,
+ need_penalties = need_penalties}, after)
+ tree.analyze_callback(head)
+ end
+
+ return retval
+ end
+}
+
+-- The actual callback functions. The `*_before` and `*_after`
+-- variants are needed for luatexbase. For 'exclusive' callbacks we
+-- directly use the corresponding functions from the
+-- `callback_wrappers` table.
+
+local callbacks = {
+ contribute_filter = function(extrainfo)
+ callback_wrappers.contribute_filter(extrainfo, 'contribute_filter')
+ end,
+ contribute_filter_before = function(extrainfo)
+ callback_wrappers.contribute_filter(extrainfo, 'before')
+ end,
+ contribute_filter_after = function(extrainfo)
+ callback_wrappers.contribute_filter(extrainfo, 'after')
+ end,
+
+ buildpage_filter = function(extrainfo)
+ callback_wrappers.buildpage_filter(extrainfo, 'buildpage_filter')
+ end,
+ buildpage_filter_before = function(extrainfo)
+ callback_wrappers.buildpage_filter(extrainfo, 'before')
+ end,
+ buildpage_filter_after = function(extrainfo)
+ callback_wrappers.buildpage_filter(extrainfo, 'after')
+ end,
+
+ build_page_insert = callback_wrappers.build_page_insert,
+
+ pre_linebreak_filter = function(head, groupcode)
+ return callback_wrappers.pre_linebreak_filter(head, groupcode,
+ 'pre_linebreak_filter')
+ end,
+ pre_linebreak_filter_before = function(head, groupcode)
+ return callback_wrappers.pre_linebreak_filter(head, groupcode, 'before')
+ end,
+ pre_linebreak_filter_after = function(head, groupcode)
+ return callback_wrappers.pre_linebreak_filter(head, groupcode, 'after')
end,
+
+ linebreak_filter = callback_wrappers.linebreak_filter,
+ append_to_vlist_filter = callback_wrappers.append_to_vlist_filter,
+
+ post_linebreak_filter = function(head, groupcode)
+ return callback_wrappers.post_linebreak_filter(head, groupcode,
+ 'post_linebreak_filter')
+ end,
+ post_linebreak_filter_before = function(head, groupcode)
+ return callback_wrappers.post_linebreak_filter(head, groupcode, 'before')
+ end,
+ post_linebreak_filter_after = function(head, groupcode)
+ return callback_wrappers.post_linebreak_filter(head, groupcode, 'after')
+ end,
+
+ hpack_filter = function(head, groupcode, size, packtype,
+ direction, attributelist)
+ return callback_wrappers.hpack_filter(head, groupcode, size, packtype,
+ direction, attributelist,
+ 'hpack_filter')
+ end,
+ hpack_filter_before = function(head, groupcode, size, packtype,
+ direction, attributelist)
+ return callback_wrappers.hpack_filter(head, groupcode, size, packtype,
+ direction, attributelist, 'before')
+ end,
+ hpack_filter_after = function(head, groupcode, size, packtype,
+ direction, attributelist)
+ return callback_wrappers.hpack_filter(head, groupcode, size, packtype,
+ direction, attributelist, 'after')
+ end,
+
+ vpack_filter = function(head, groupcode, size, packtype,
+ maxdepth, direction, attributelist)
+ return callback_wrappers.vpack_filter(head, groupcode, size, packtype,
+ maxdepth, direction, attributelist,
+ 'vpack_filter')
+ end,
+ vpack_filter_before = function(head, groupcode, size, packtype,
+ maxdepth, direction, attributelist)
+ return callback_wrappers.vpack_filter(head, groupcode, size, packtype,
+ maxdepth, direction, attributelist,
+ 'before')
+ end,
+ vpack_filter_after = function(head, groupcode, size, packtype,
+ maxdepth, direction, attributelist)
+ callback_wrappers.vpack_filter(head, groupcode, size, packtype,
+ maxdepth, direction, attributelist,
+ 'after')
+ end,
+
+ hpack_quality = callback_wrappers.hpack_quality,
+ vpack_quality = callback_wrappers.vpack_quality,
+ process_rule = callback_wrappers.process_rule,
+
+ pre_output_filter = function(head, groupcode, size, packtype,
+ maxdepth, direction)
+ return callback_wrappers.pre_output_filter(head, groupcode, size,
+ packtype, maxdepth, direction,
+ 'pre_output_filter')
+ end,
+ pre_output_filter_before = function(head, groupcode, size, packtype,
+ maxdepth, direction)
+ return callback_wrappers.pre_output_filter(head, groupcode, size,
+ packtype, maxdepth, direction,
+ 'before')
+ end,
+ pre_output_filter_after = function(head, groupcode, size, packtype,
+ maxdepth, direction)
+ return callback_wrappers.pre_output_filter(head, groupcode, size,
+ packtype, maxdepth, direction,
+ 'after')
+ end,
+
+ hyphenate = function(head, tail)
+ callback_wrappers.hyphenate(head, tail, 'hyphenate')
+ end,
+ hyphenate_before = function(head, tail)
+ callback_wrappers.hyphenate(head, tail, 'before')
+ end,
+ hyphenate_after = function(head, tail)
+ callback_wrappers.hyphenate(head, tail, 'after')
+ end,
+
+ ligaturing = function(head, tail)
+ callback_wrappers.ligaturing(head, tail, 'ligaturing')
+ end,
+ ligaturing_before = function(head, tail)
+ callback_wrappers.ligaturing(head, tail, 'before')
+ end,
+ ligaturing_after = function(head, tail)
+ callback_wrappers.ligaturing(head, tail, 'after')
+ end,
+
+ kerning = function(head, tail)
+ callback_wrappers.kerning(head, tail, 'kerning')
+ end,
+ kerning_before = function(head, tail)
+ callback_wrappers.kerning(head, tail, 'before')
+ end,
+ kerning_after = function(head, tail)
+ callback_wrappers.kerning(head, tail, 'after')
+ end,
+
+ insert_local_par = function(head, tail)
+ callback_wrappers.insert_local_par(head, tail, 'insert_local_par')
+ end,
+ insert_local_par_before = function(head, tail)
+ callback_wrappers.insert_local_par(head, tail, 'before')
+ end,
+ insert_local_par_after = function(head, tail)
+ callback_wrappers.insert_local_par(head, tail, 'after')
+ end,
+
+ mlist_to_hlist = callback_wrappers.mlist_to_hlist
}
---- Set a single option key value pair.
---
+--- Messages, options
+---
+---@section messages
+
+--- Emit a warning or error message.
+---
+---This works for plain TeX, Texinfo, and LaTeX (for plain TeX and
+---Texinfo we make the message look identical to the LaTeX case).
+---Note that a full stop gets appended to `what`.
+---
+---@param why string # `'error'` or `'warning'`.
+---@param where string # In which package the error happened.
+---@param what string # The warning message to emit.
+---@param help? string # Additional help text for errors.
+local function message(why, where, what, help)
+ local msg
+
+ what = string.gsub(what, '\n', '\\MessageBreak ')
+
+ if why == 'error' then
+ if not help then
+ help = ''
+ end
+
+ msg = {
+ '\\ifx\\mbox\\undefined',
+ ' \\errhelp{' .. help .. '}%',
+ ' \\begingroup',
+ ' \\newlinechar`\\^^J%',
+ ' \\def\\MessageBreak{^^J(' .. where .. ')' .. string.rep('\\space', 16) .. '}%',
+ ' \\errmessage{Package ' .. where .. ' Error: ' .. what .. '}%',
+ ' \\endgroup',
+ '\\else',
+ ' \\PackageError{' .. where .. '}{' .. what .. '}{' .. help .. '}%',
+ '\\fi'
+ }
+ else
+ msg = {
+ '\\ifx\\mbox\\undefined',
+ ' \\begingroup',
+ ' \\newlinechar`\\^^J%',
+ ' \\def\\MessageBreak{^^J(' .. where .. ')' .. string.rep('\\space', 16) .. '}%',
+ ' \\message{Package ' .. where .. ' Warning: ' .. what .. '}%',
+ ' \\endgroup',
+ '\\else',
+ ' \\PackageWarning{' .. where .. '}{' .. what .. '}%',
+ '\\fi'
+ }
+ end
+
+ if tex.escapechar == utf8.codepoint('@') then
+ table.insert(msg, 1, '@tex')
+ table.insert(msg, '@end tex')
+ end
+
+ tex.print(msg)
+end
+
+--- Set a single-option key-value pair.
+---
---@param key string # The key of the option pair.
---@param value number|string # The value of the option pair.
local function set_option(key, value)
+ if not default_options[key] then
+ message(
+ 'warning',
+ 'nodetree',
+ "Ignoring unknown option '" .. key .. "'"
+ )
+ return
+ end
if not options then
options = {}
end
- if key == 'verbosity' or key == 'decimalplaces' then
- options[key] = tonumber(value)
+ if key == 'verbosity' then
+ options[key] = tonumber(value) or default_options.verbosity
+ elseif key == 'decimalplaces' then
+ options[key] = tonumber(value) or default_options.decimalplaces
+ elseif key == 'firstline' then
+ options[key] = tonumber(value) or default_options.firstline
+ elseif key == 'lastline' then
+ options[key] = tonumber(value) or default_options.lastline
else
options[key] = value
end
end
---- Set multiple key value pairs using a table.
---
----@param opts table # Options
+--- Set multiple key-value option pairs using a table.
+---
+---@param opts table # Options.
local function set_options(opts)
if not options then
options = {}
@@ -1479,34 +2230,74 @@ local function set_options(opts)
end
end
---- Check if the given callback name exists.
---
--- Throw an error if it doen’t.
---
+--- Callback management
+---
+---@section callback_management
+
+---
+---@param what? string|'before'|'after' # The name of a callback, or either the string `before` or `after`.
+---
+---@return 'before'|nil # 'before'` or `nil`.
+---@return 'after'|nil # `'after'` or `nil`.
+function template.get_print_position(what)
+ local before, after
+
+ if what == 'before' then
+ before = what
+ after = nil
+ elseif what == 'after' then
+ before = nil
+ after = what
+ else
+ before = print_positions[what][1]
+ after = print_positions[what][2]
+ end
+
+ return before, after
+end
+
+---
+---@param name string
+---@param internal? string|boolean
+function template.no_callback(name, internal)
+ local more = ''
+ if internal == true then
+ more = ',' .. format.new_line() ..
+ ' LuaTeX uses internal function instead'
+ end
+ nodetree_print(
+ format.new_line() ..
+ "<no registered function for '" ..
+ format.underscore(name) .. "' callback" .. more .. ">")
+end
+
+--- Check whether the given callback name exists.
+---
+---Throw an error if it doesn’t.
+---
---@param callback_name string # The name of a callback to check.
---
+---
---@return string # The unchanged input of the function.
local function check_callback_name(callback_name)
local info = callback.list()
if info[callback_name] == nil then
- tex.error(
- 'Package "nodetree": Unkown callback name or callback alias: "' ..
- callback_name ..
- '"'
+ message(
+ 'error',
+ 'nodetree',
+ 'Unknown callback name or callback alias\n'
+ .. "'" .. callback_name .. "'"
)
end
return callback_name
end
--- Get the real callback name from an alias string.
---
----@param alias string The alias of a callback name or the callback
--- name itself.
---
+---
+---@param alias string # The alias of a callback name or the callback name itself.
+---
---@return string # The real callback name.
local function get_callback_name(alias)
local callback_name
- -- Listed as in the LuaTeX reference manual.
if alias == 'contribute' or alias == 'contributefilter' then
callback_name = 'contribute_filter'
@@ -1527,7 +2318,6 @@ local function get_callback_name(alias)
elseif alias == 'append' or alias == 'appendtovlistfilter' then
callback_name = 'append_to_vlist_filter'
- -- postlinebreak is not documented.
elseif alias == 'postline' or alias == 'postlinebreak' or alias == 'postlinebreakfilter' then
callback_name = 'post_linebreak_filter'
@@ -1571,65 +2361,194 @@ local function get_callback_name(alias)
end
--- Register a callback.
---
+---
+--- Doing this for plain TeX is simple; we have access to LuaTeX's
+--- base function `callback.register` and thus can easily add our
+--- callback wrapper, which in turn calls nodetree's functions at the
+--- very beginning and/or at the very end.
+---
+--- Enter LaTeX. It comes with its own callback management that can
+--- register multiple callbacks, also taking care of the calling
+--- order. Unfortunately, however, it is also more restrictive: for
+--- example, some callbacks like `linebreak_filter` are tagged as
+--- 'exclusive', only accepting a single callback. While this makes
+--- sense for the end user, it complicates the situation for nodetree
+--- to install its non-intrusive, observing-only callbacks.
+---
+--- We thus take the following route.
+---
+--- * If there is no function for callback `<foo>` installed, register
+--- `callbacks.<foo>`.
+---
+--- * If there is a (single) function for callback `<foo>` of type
+--- three ('exclusive'), remove it, wrap it into `callbacks.<foo>`
+--- (via `orig_callbacks`) and install `callbacks.<foo>`.
+---
+--- * Otherwise register `callbacks.<foo>_{before,after}` as
+--- necessary.
+---
---@param cb string # The name of a callback.
local function register_callback(cb)
if luatexbase then
- luatexbase.add_to_callback(cb, callbacks[cb], 'nodetree')
+ local descriptions = luatexbase.callback_descriptions(cb)
+
+ if #descriptions == 0 then
+ -- No callback installed. If there is no default action
+ -- (according to the LuaTeX manual), use only `before`, ignoring
+ -- the position requested by the user.
+ if not callback_has_default_action[cb] then
+ print_positions[cb] = { 'before', nil }
+ end
+ luatexbase.add_to_callback(cb, callbacks[cb], 'nodetree')
+ elseif luatexbase.callbacktypes[cb] == 3 then
+ -- A single, 'exclusive' callback.
+ orig_callbacks[cb], orig_descriptions[cb] =
+ luatexbase.remove_from_callback(cb, descriptions[1])
+ luatexbase.add_to_callback(cb, callbacks[cb], 'nodetree')
+ else
+ -- All other callback types.
+ local funcs = {}
+ local descr = {}
+ local before, after = template.get_print_position(cb)
+
+ -- XXX Is this correct for 'reverselist' callback type?
+
+ -- This makes the callback wrapper call neither the old nor the
+ -- new function.
+ orig_callbacks[cb] = ''
+
+ for i, description in ipairs(descriptions) do
+ funcs[i], descr[i] = luatexbase.remove_from_callback(cb, description)
+ end
+
+ if before then
+ luatexbase.add_to_callback(cb, callbacks[cb .. '_before'],
+ 'nodetree before')
+ end
+ for i, _ in ipairs(funcs) do
+ luatexbase.add_to_callback(cb, funcs[i], descr[i])
+ end
+ if after then
+ luatexbase.add_to_callback(cb, callbacks[cb .. '_after'],
+ 'nodetree after')
+ end
+ end
else
+ orig_callbacks[cb] = callback.find(cb)
callback.register(cb, callbacks[cb])
end
end
--- Unregister a callback.
---
+---
+--- We follow the same logic as with `register_callback`.
+---
---@param cb string # The name of a callback.
local function unregister_callback(cb)
if luatexbase then
- luatexbase.remove_from_callback(cb, 'nodetree')
+ local descriptions = luatexbase.callback_descriptions(cb)
+
+ if #descriptions == 0 then
+ return
+ elseif #descriptions == 1 then
+ luatexbase.remove_from_callback(cb, 'nodetree')
+ if orig_callbacks[cb] then
+ luatexbase.add_to_callback(cb,
+ orig_callbacks[cb],
+ orig_descriptions[cb])
+ end
+ orig_callbacks[cb] = nil
+ orig_descriptions[cb] = nil
+ else
+ local funcs = {}
+ local descr = {}
+
+ local i = 1
+ for _, description in ipairs(descriptions) do
+ if description == 'nodetree before' or
+ description == 'nodetree after' then
+ luatexbase.remove_from_callback(cb, description)
+ else
+ funcs[i], descr[i] = luatexbase.remove_from_callback(cb,
+ description)
+ i = i + 1
+ end
+ end
+
+ for n, _ in ipairs(funcs) do
+ luatexbase.add_to_callback(cb, funcs[n], descr[n])
+ end
+ end
else
- register_callback(cb, nil)
+ callback.register(cb, nil)
+ callback.register(cb, orig_callbacks[cb])
end
end
--- Exported functions.
--- @section export
+---
+---@section export
local export = {
set_option = set_option,
set_options = set_options,
- ---
+ ---@function export.register_callbacks
register_callbacks = function()
if options.channel == 'log' or options.channel == 'tex' then
-- nt = nodetree
-- jobname.nttex
-- jobname.ntlog
local file_name = tex.jobname .. '.nt' .. options.channel
- io.open(file_name, 'w'):close() -- Clear former content
+ io.open(file_name, 'w'):close() -- Clear former content.
output_file = io.open(file_name, 'a')
end
+
+ -- Split string at ','.
for alias in string.gmatch(options.callback, '([^,]+)') do
- register_callback(get_callback_name(alias))
+ -- Trim whitespace.
+ alias = string.gsub(alias, '^%s*(.-)%s*$', '%1')
+
+ -- Check where to position nodetree's inspection callback(s).
+ local before, after
+ if string.sub(alias, 1, 1) == ':' then
+ before = 'before'
+ alias = string.sub(alias, 2, -1)
+ end
+ if string.sub(alias, -1, -1) == ':' then
+ after = 'after'
+ alias = string.sub(alias, 1, -2)
+ end
+ if not before and not after then
+ before = 'before'
+ end
+ local name = get_callback_name(alias)
+ print_positions[name] = {before, after}
+ register_callback(name)
end
end,
- ---
+ ---@function export.unregister_callbacks
unregister_callbacks = function()
for alias in string.gmatch(options.callback, '([^,]+)') do
- unregister_callback(get_callback_name(alias))
+ -- Split string at ',', then trim whitespace. For symmetry with
+ -- `register_callbacks`, also remove a leading and/or trailing
+ -- ':' character.
+ unregister_callback(
+ get_callback_name(string.gsub(alias, '^%s*:?(.-):?%s*$', '%1'))
+ )
end
end,
--- Compile a TeX snippet.
--
-- Write some TeX snippets into a temporary LaTeX file, compile this
- -- file using `latexmk` and read the generated `*.nttex` file and
+ -- file using `latexmk`, read the generated `*.nttex` file, and
-- return its content.
--
- ---@param tex_markup string
+ ---@function export.compile_include
--
- ---@return string
+ ---@param tex_markup string
compile_include = function(tex_markup)
-- Generate a subfolder for all tempory files: _nodetree-jobname.
local parent_path = lfs.currentdir() .. '/' .. '_nodetree-' .. tex.jobname
@@ -1641,12 +2560,12 @@ local export = {
local absolute_path_tex = parent_path .. '/' .. filename_tex
output_file = io.open(absolute_path_tex, 'w')
- local format_option = function (key, value)
+ local format_option = function(key, value)
return '\\NodetreeSetOption[' .. key .. ']{' .. value .. '}' .. '\n'
end
- -- Process the options
- local options =
+ -- Process the options.
+ local option_lines =
format_option('channel', 'tex') ..
format_option('verbosity', options.verbosity) ..
format_option('unit', options.unit) ..
@@ -1657,32 +2576,58 @@ local export = {
local prefix = '%!TEX program = lualatex\n' ..
'\\documentclass{article}\n' ..
'\\usepackage{nodetree}\n' ..
- options .. '\n' ..
+ option_lines .. '\n' ..
'\\begin{document}\n'
local suffix = '\n\\end{document}'
- output_file:write(prefix .. tex_markup .. suffix)
- output_file:close()
+ if output_file ~= nil then
+ output_file:write(prefix .. tex_markup .. suffix)
+ output_file:close()
+ end
-- Compile the temporary LuaTeX or LuaLaTeX file.
os.spawn({ 'latexmk', '-cd', '-pdflua', absolute_path_tex })
- local include_file = assert(io.open(parent_path .. '/' .. example_counter .. '.nttex', 'rb'))
- local include_content = include_file:read("*all")
+ local include_file = assert(io.open(parent_path .. '/' .. example_counter .. '.nttex', 'r'))
+ local include_content = include_file:read('*all')
include_file:close()
- include_content = include_content:gsub('[\r\n]', '')
+ -- To make the newline character be handled properly by the TeX engine
+ -- it would be necessary to set up its correct catcode. However, it is
+ -- simpler to just replace all newlines with '{}'.
+ include_content = include_content:gsub('[\r\n]', '{}')
tex.print(include_content)
end,
- --- Check for `--shell-escape`
- --
- check_shell_escape = function()
+ --- Check for `\--shell-escape` within a command or environment.
+ ---
+ ---@function export.check_shell_escape
+ ---
+ ---@param what string # The name of the command or environment.
+ ---@param is_command boolean # Set if `what` is a command.
+ check_shell_escape = function(what, is_command)
local info = status.list()
- if info.shell_escape == 0 then
- tex.error('Package "nodetree-embed": You have to use the --shell-escape option')
+ if info ~= nil and info.shell_escape ~= 1 then
+ local typ, stuff
+ if is_command == true then
+ typ = 'command'
+ stuff = 'argument'
+ else
+ typ = 'environment'
+ stuff = 'contents'
+ end
+ message(
+ 'error',
+ 'nodetree-embed',
+ what .. ' needs option --shell-escape',
+ "You must process this document with 'lualatex --shell-escape ...'\n"
+ .. "so that 'latexmk' can be executed to generate the nodetree view\n"
+ .. 'for the ' .. stuff .. ' of this ' .. typ .. '.'
+ )
end
end,
--- Print a node tree.
---
+ ---@function export.print
+ ---
---@param head Node # The head node of a node list.
---@param opts table # Options as a table.
print = function(head, opts)
@@ -1693,9 +2638,11 @@ local export = {
tree.analyze_list(head, 1)
end,
- --- Format a scaled point value into a formated string.
+ --- Format a scaled point value as a formatted string.
--
- ---@param sp number # A scaled point value
+ ---@function export.format_dim
+ ---
+ ---@param sp number # A scaled point value.
--
---@return string
format_dim = function(sp)
@@ -1703,17 +2650,98 @@ local export = {
end,
--- Get a default option that is not changed.
+ ---
+ ---@function export.get_default_option
+ ---
---@param key string # The key of the option.
--
---@return string|number|boolean
get_default_option = function(key)
return default_options[key]
+ end,
+
+ --- Push current options.
+ ---
+ ---@function export.push_options
+ push_options = function()
+ prev_options[option_level] = {}
+ for k, v in pairs(options) do
+ prev_options[option_level][k] = v
+ end
+ option_level = option_level + 1
+ end,
+
+ --- Pop previous options.
+ ---
+ ---@function export.pop_options
+ pop_options = function()
+ if option_level > 0 then
+ prev_options[option_level] = nil
+ option_level = option_level - 1
+ for k, v in pairs(prev_options[option_level]) do
+ options[k] = v
+ end
+ end
+ end,
+
+ --- Read a LaTeX input file and emit it immediately, obeying options
+ --- `firstline` and `lastline`.
+ ---
+ ---@function export.input
+ ---
+ ---@param filename string
+ input = function(filename)
+ local file = assert(io.open(filename, 'r'))
+ local lines_in = {}
+ for line in file:lines() do
+ table.insert(lines_in, line)
+ end
+
+ local firstline = options.firstline
+ local lastline = options.lastline
+
+ -- Handle negative line numbers.
+ if firstline < 0 then
+ firstline = #lines_in + 1 + firstline
+ elseif firstline == 0 then
+ firstline = 1
+ end
+ if lastline < 0 then
+ lastline = #lines_in + 1 + lastline
+ elseif lastline == 0 then
+ lastline = 1
+ end
+
+ -- Clamp values.
+ if firstline < 1 then
+ firstline = 1
+ elseif firstline > #lines_in then
+ firstline = #lines_in
+ end
+ if lastline < 1 then
+ lastline = 1
+ elseif lastline > #lines_in then
+ lastline = #lines_in
+ end
+
+ if firstline > lastline then
+ local tmp = firstline
+ firstline = lastline
+ lastline = tmp
+ end
+
+ local lines_out = {}
+ for i, line in ipairs(lines_in) do
+ if firstline <= i and i <= lastline then
+ table.insert(lines_out, line)
+ end
+ end
+
+ tex.print(lines_out)
end
}
---- Use export.print
---
----@param head Node # The head node of a node list.
+--- Set to `export.print`.
export.analyze = export.print
return export
diff --git a/macros/luatex/generic/nodetree/nodetree.pdf b/macros/luatex/generic/nodetree/nodetree.pdf
index c045405ed2..c15b1e5c17 100644
--- a/macros/luatex/generic/nodetree/nodetree.pdf
+++ b/macros/luatex/generic/nodetree/nodetree.pdf
Binary files differ