% \iffalse meta-comment % % Copyright (C) 2016 by Josef Friedrich % ---------------------------------------------------------------------- % This work may be distributed and/or modified under the conditions of % the LaTeX Project Public License, either version 1.3 of this license % or (at your option) any later version. The latest version of this % license is in: % % http://www.latex-project.org/lppl.txt % % and version 1.3 or later is part of all distributions of LaTeX % version 2005/12/01 or later. % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is Josef Friedrich. % % This work consists of the files nodetree.dtx and nodetree.ins % and the derived filebase nodetree.sty and nodetree.lua. % % \fi % % \iffalse %<*driver> \ProvidesFile{nodetree.dtx} % %\NeedsTeXFormat{LaTeX2e}[1999/12/01] %\ProvidesPackage{nodetree} %<*package> [2016/07/18 v1.2 Visualize node lists in a tree view] % %<*driver> \documentclass{ltxdoc} \usepackage{paralist,fontspec,graphicx,fancyvrb} \usepackage[ colorlinks=true, linkcolor=red, filecolor=red, urlcolor=red, ]{hyperref} %\usepackage{nodetree} \EnableCrossrefs \CodelineIndex \RecordChanges \setmonofont{DejaVu Sans Mono} \def\nodetreelua#1{\texttt{\scantokens{\catcode`\_=12\relax#1}}} \def\secref#1{(\rightarrow\ \ref{#1})} \newcommand{\tmpgraphics}[1]{ \noindent \includegraphics[scale=0.4]{graphics/#1} } \DefineVerbatimEnvironment{code}{Verbatim} { frame=single, fontsize=\footnotesize, } \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}% } \date{\fileversion~from \filedate} \maketitle \noindent \includegraphics[width=\linewidth]{graphics/packagename} \newpage \tableofcontents \newpage %----------------------------------------------------------------------- % Abstract %----------------------------------------------------------------------- \section{Abstract} |nodetree| is a development package that visualizes the structure of node lists. |nodetree| shows its debug informations in the consoles’ output when you compile a Lua\TeX{} file. It uses a similar visual representation for node lists as the UNIX |tree| command uses for a folder structure. Node lists are the main building blocks of each document generated by the \TeX{} engine \emph{Lua\TeX}. The package |nodetree| doesn‘t change the rendered document. The tree view can only be seen when using a terminal to generate the document. |nodetree| is inspired by a \href{https://gist.github.com/pgundlach/556247} {gist from Patrick Gundlach}. %----------------------------------------------------------------------- % Usage %----------------------------------------------------------------------- \section{Usage} The package |nodetree| can be used both with Lua\TeX{} and Lua\LaTeX{}. You have to use both engines in a text console. Run for example |luatex luatex-test.tex| to list the nodes using Lua\TeX{}. \begin{code} \input{nodetree.tex} \nodetreeregister{postline} Lorem ipsum dolor. \bye \end{code} Or run |lualatex lualatex-test.tex| to show a node tree using Lua\LaTeX{}. In Lua\LaTeX{} you can omit |\nodetreeregister{postline}|. |\usepackage{nodetree}| registers automatically the |post_linebreak_filter|. If you don’t want debug the |post_linebreak_filter| use |\nodetreeunregister{postline}|. \begin{code} \documentclass{article} \usepackage{nodetree} \begin{document} Lorem ipsum dolor. \end{document} \end{code} %% % inside Lua code %% \subsection{Debug nodes inside Lua code} Use the Lua function |nodetree.analyze(head)| to debug nodes inside your Lua code. The following code snippet demonstrates the usage in Lua\TeX{}. |head| is the current node. \begin{code} \input{nodetree.tex} \directlua{ local test = function (head) nodetree.analyze(head) end callback.register('post_linebreak_filter', test) } Lorem ipsum dolor. \bye \end{code} This example illustrates how the function has to be applied in Lua\LaTeX{}. \begin{code} \documentclass{article} \usepackage{nodetree} \begin{document} \directlua{ local test = function (head) nodetree.analyze(head) end luatexbase.add_to_callback('post_linebreak_filter', test, 'test') } Lorem ipsum dolor. \end{document} \end{code} %----------------------------------------------------------------------- % Macros %----------------------------------------------------------------------- \section{Macros} %% % \nodetreeregister %% \subsection{\cmd{\nodetreeregister}} \DescribeMacro{\nodetreeregister} \cmd{\nodetreeregister}\marg{callbacks}: The argument \marg{callbacks} takes a comma separated list of callback aliases as described in \secref{sec:option-callback}. %% % \nodetreeunregister %% \subsection{\cmd{\nodetreeunregister}} \DescribeMacro{\nodetreeunregister} \cmd{\nodetreeunregister}\marg{callbacks}: The argument \marg{callbacks} takes a comma separated list of callback aliases as described in \secref{sec:option-callback}. %% % \nodetreeoption %% \subsection{\cmd{\nodetreeoption}} \DescribeMacro{\nodetreeoption} \cmd{\nodetreeoption}\oarg{option}\marg{value}: \secref{sec:options} This macro sets the option \oarg{option} to the value \marg{value}. %% % \nodetreeset %% \subsection{\cmd{\nodetreeset}} \DescribeMacro{\nodetreeset} \cmd{\nodetreeset}\marg{kv-options}: This macro can only be used in Lua\LaTeX{}. \marg{kv-options} are key value pairs. \begin{code} \nodetreeset{color=no,callbacks={hpack,vpack},verbosity=2} \end{code} %----------------------------------------------------------------------- % Options %----------------------------------------------------------------------- \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. You have to specify one alias to select the |callback|. Because of the underscores the callback name contains it can not set by its technical name (\rightarrow{} Figure \ref{fig:callback}). This macros process callback options: \cmd{\nodetreeregister}\marg{callbacks}, \cmd{\nodetreeunregister}\marg{callbacks}, \cmd{\nodetreeset}\marg{callback=} and \cmd{\usepackage}\oarg{callback=}\marg{nodetree}. Use commas to specify mulitple callbacks. Avoid using whitespaces: \begin{code} \nodetreeregister{preline,line,postline} \end{code} Wrap your callback aliases in curly braces for the macro |\nodetreeset|: \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} %% % Tabular callbacks %% \newcommand{\nodetreecallback}[3]{ \nodetreelua{#1} & \nodetreelua{#2} & \nodetreelua{#3} \\ } \begin{figure} \noindent \begin{tabular}{lll} \textbf{Alias (short)} & \textbf{Alias (longer)} & \textbf{Callback} \\ \nodetreecallback{contribute}{contributefilter}{contribute_filter} \nodetreecallback{buildpage}{buildpagefilter}{buildpage_filter} \nodetreecallback{preline}{prelinebreakfilter}{pre_linebreak_filter} \nodetreecallback{line}{linebreakfilter}{linebreak_filter} \nodetreecallback{append}{appendtovlistfilter}{append_to_vlist_filter} \nodetreecallback{postline}{postlinebreakfilter}{post_linebreak_filter} \nodetreecallback{hpack}{hpackfilter}{hpack_filter} \nodetreecallback{vpack}{vpackfilter}{vpack_filter} \nodetreecallback{hpackq}{hpackquality}{hpack_quality} \nodetreecallback{vpackq}{vpackquality}{vpack_quality} \nodetreecallback{process}{processrule}{process_rule} \nodetreecallback{preout}{preoutputfilter}{pre_output_filter} \nodetreecallback{hyph}{hyphenate}{hyphenate} \nodetreecallback{liga}{ligaturing}{ligaturing} \nodetreecallback{kern}{kerning}{kerning} \nodetreecallback{insert}{insertlocalpar}{insert_local_par} \nodetreecallback{mhlist}{mlisttohlist}{mlist_to_hlist} \end{tabular} \caption{The callback aliases} \label{fig:callback} \end{figure} %% % verbosity %% \subsection{Option \texttt{verbosity}} Higher integer values result in a more verbose output. The default value for this options is |1|. At the moment only verbosity level |2| is implemented. %% % color %% \subsection{Option \texttt{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}} The option |unit| sets the length unit to display all length values of the nodes. The default option for |unit| is |pt|. See figure \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} %% % decimalplaces %% \subsection{Option \texttt{decimalplaces}} The options |decimalplaces| sets the number of decimal places for some node fields. \begin{code} \nodetreeoption[decimalplaces]{4} \end{code} gets \begin{code} ├─GLYPH char: "a"; width: 5pt; height: 4.3055pt; \end{code} If |decimalplaces| is set to |0| only integer values are shown. \begin{code} ├─GLYPH char: "a"; width: 5pt; height: 4pt; \end{code} %----------------------------------------------------------------------- % Visual tree structure %----------------------------------------------------------------------- \section{Visual tree structure} %% % Two different connections %% \subsection{Two different connections} Nodes in Lua\TeX{} are connected. The |nodetree| package distinguishs between the |list| and |field| connections. \begin{itemize} \item |list|: Nodes, which are double connected by |next| and |previous| fields. \item |field|: Connections to nodes by other fields than |next| and |previous| fields, e. g. |head|, |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. \noindent \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} For |list| connections \emph{light} characters are shown. \begin{code} │ │ │ ├─list1 │ └─list2 └─list3 \end{code} |field| connections are visialized by \emph{Double} characters. \begin{code} ║ ║ ║ ╠═field1 ║ ╚═field2 ╚═field3 \end{code} %----------------------------------------------------------------------- % Examples %----------------------------------------------------------------------- \newpage \section{Examples} %% % packagename %% \subsection{The node list of the package name} \begin{code} \documentclass{article} \usepackage{nodetree} \begin{document} nodetree \end{document} \end{code} \tmpgraphics{packagename} %% % math %% \newpage \subsection{The node list of a mathematical formula} \begin{code} \documentclass{article} \usepackage[callback={mhlist}]{nodetree} \begin{document} \[\left(a\right)\left[\frac{b}{a}\right]=a\,\] \end{document} \end{code} \tmpgraphics{math} %% % ligatures %% \newpage \subsection{The node list of the word \emph{Office}} The characters \emph{ffi} are deeply nested in a discretionary node. \begin{code} \documentclass{article} \usepackage{nodetree} \begin{document} Office \end{document} \end{code} \tmpgraphics{ligatures} %----------------------------------------------------------------------- % Index %----------------------------------------------------------------------- \DocInput{nodetree.dtx} \pagebreak \PrintChanges \pagebreak \PrintIndex \end{document} % %<*readme> ![nodetree](https://raw.githubusercontent.com/Josef-Friedrich/nodetree/master/graphics/packagename.png) # Abstract `nodetree` is a development package that visualizes the structure of node lists. `nodetree` shows its debug informations in the consoles’ output when you compile a LuaTeX file. It uses a similar visual representation for node lists as the UNIX `tree` command uses for a folder structure. Node lists are the main building blocks of each document generated by the TeX engine LuaTeX. The package `nodetree` doesn‘t change the rendered document. The tree view can only be seen when using a terminal to generate the document. `nodetree` is inspired by a [gist from Patrick Gundlach](https://gist.github.com/pgundlach/556247). # License Copyright (C) 2016 by Josef Friedrich ------------------------------------------------------------------------ This work may be distributed and/or modified under the conditions of the LaTeX Project Public License, either version 1.3 of this license or (at your option) any later version. The latest version of this license is in: http://www.latex-project.org/lppl.txt and version 1.3 or later is part of all distributions of LaTeX version 2005/12/01 or later. # CTAN Since July 2016 the cloze package is included in the Comprehensive TeX Archive Network (CTAN). * TeX archive: http://mirror.ctan.org/tex-archive/macros/luatex/generic/nodetree * Package page: http://www.ctan.org/pkg/nodetree # Repository https://github.com/Josef-Friedrich/nodetree # Installation Get source: git clone git@github.com:Josef-Friedrich/nodetree.git cd nodetree Compile: make or manually: luatex nodetree.ins lualatex nodetree.dtx makeindex -s gglo.ist -o nodetree.gls nodetree.glo makeindex -s gind.ist -o nodetree.ind nodetree.idx lualatex nodetree.dtx # Examples ## The node list of the package name ```latex \documentclass{article} \usepackage{nodetree} \begin{document} nodetree \end{document} ``` ![nodetree](graphics/packagename.png) ## The node list of a mathematical formula ```latex \documentclass{article} \usepackage[callback={mhlist}]{nodetree} \begin{document} \[\left(a\right)\left[\frac{b}{a}\right]=a\,\] \end{document} ``` ![nodetree](https://raw.githubusercontent.com/Josef-Friedrich/nodetree/master/graphics/math.png) ## The node list of the word 'Office' The characters 'ffi' are deeply nested in a discretionary node. ```latex \documentclass{article} \usepackage{nodetree} \begin{document} Office \end{document} ``` ![nodetree](https://raw.githubusercontent.com/Josef-Friedrich/nodetree/master/graphics/ligatures.png) % % \fi % % \CheckSum{0} % % \CharacterTable % {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z % Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z % Digits \0\1\2\3\4\5\6\7\8\9 % Exclamation \! Double quote \" Hash (number) \# % Dollar \$ Percent \% Ampersand \& % Acute accent \' Left paren \( Right paren \) % Asterisk \* Plus \+ Comma \, % Minus \- Point \. Solidus \/ % Colon \: Semicolon \; Less than \< % Equals \= Greater than \> Question mark \? % Commercial at \@ Left bracket \[ Backslash \\ % Right bracket \] Circumflex \^ Underscore \_ % 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} % % \DoNotIndex{\newcommand,\newenvironment,\def,\directlua} % % \StopEventually{} % \pagebreak % \section{Implementation} % % \iffalse %<*tex> % \fi % \MacroTopsep = 10pt plus 2pt minus 2pt % \MacrocodeTopsep = 10pt plus 1.2pt minus 1pt % \makeatletter % \c@CodelineNo 25 \relax % \makeatother % % \subsection{The file \tt{nodetree.tex}} % % \begin{macrocode} \directlua{ nodetree = require('nodetree') nodetree.set_option('engine', 'luatex') nodetree.set_default_options() } % \end{macrocode} % % \begin{macro}{\nodetreeoption} % \begin{macrocode} \def\nodetreeoption[#1]#2{ \directlua{ nodetree.set_option('#1', '#2') } } % \end{macrocode} % \end{macro} % % \begin{macro}{\nodetreeregister} % \begin{macrocode} \def\nodetreeregister#1{ \directlua{ nodetree.set_option('callback', '#1') nodetree.register_callbacks() } } % \end{macrocode} % \end{macro} % % \begin{macro}{\nodetreeunregister} % \begin{macrocode} \def\nodetreeunregister#1{ \directlua{ nodetree.set_option('callback', '#1') nodetree.unregister_callbacks() } } % \end{macrocode} % \end{macro} % % \iffalse % %<*package> % \fi % \makeatletter % \c@CodelineNo 25 \relax % \makeatother % % \subsection{The file \tt{nodetree.sty}} % % \begin{macrocode} \input{nodetree} \directlua{ nodetree.set_option('engine', 'lualatex') } % \end{macrocode} % % \begin{macrocode} \RequirePackage{kvoptions} % \end{macrocode} % % \begin{macrocode} \SetupKeyvalOptions{ family=NT, prefix=NT@ } % \end{macrocode} % % \begin{macrocode} \DeclareStringOption[term]{channel} \define@key{NT}{channel}[]{\nodetreeoption[channel]{#1}} % \end{macrocode} % % \begin{macrocode} \DeclareStringOption[postlinebreak]{callback} \define@key{NT}{callback}[]{\nodetreeoption[callback]{#1}} % \end{macrocode} % % \begin{macrocode} \DeclareStringOption[1]{verbosity} \define@key{NT}{verbosity}[]{\nodetreeoption[verbosity]{#1}} % \end{macrocode} % % \begin{macrocode} \DeclareStringOption[colored]{color} \define@key{NT}{color}[]{\nodetreeoption[color]{#1}} % \end{macrocode} % % \begin{macrocode} \DeclareStringOption[1]{unit} \define@key{NT}{unit}[]{\nodetreeoption[unit]{#1}} % \end{macrocode} % % \begin{macrocode} \DeclareStringOption[1]{decimalplaces} \define@key{NT}{decimalplaces}[]{\nodetreeoption[decimalplaces]{#1}} % \end{macrocode} % % \begin{macrocode} \ProcessKeyvalOptions* \directlua{ nodetree.set_default_options() nodetree.register_callbacks() } % \end{macrocode} % % \begin{macro}{\nodetreeset} % \begin{macrocode} \newcommand{\nodetreeset}[1]{\setkeys{nodetree}{#1}} % \end{macrocode} % \end{macro} % % \iffalse % %<*luamain> % \fi % % \makeatletter % \c@CodelineNo 0 \relax % \makeatother % % \subsection{The file \tt{nodetree.lua}} % % \begin{macrocode} local nodex = {} % \end{macrocode} % % \begin{macrocode} local tpl = {} % \end{macrocode} % % \begin{macrocode} local tree = {} % \end{macrocode} % % Nodes in Lua\TeX{} are connected. The nodetree view distinguishs % between the |list| and |field| connections. % % \begin{itemize} % \item |list|: Nodes, which are double connected by |next| and % |previous| fields. % \item |field|: Connections to nodes by other fields than |next| and % |previous| fields, e. g. |head|, |pre|. % \end{itemize} % % The lua table named |tree.state| holds state values for the current % tree item. % % \begin{code} % tree.state: % - 1: % - list: continue % - field: stop % - 2: % - list: continue % - field: stop % \end{code} % \begin{macrocode} tree.state = {} % \end{macrocode} % % \begin{macrocode} local callbacks = {} % \end{macrocode} % % \begin{macrocode} local base = {} % \end{macrocode} % % \begin{macrocode} local options = {} % \end{macrocode} % % \subsubsection{nodex --- Extend the node library} % % Get the node id form, e. g.: % \begin{code} % nil : hlist 2> % \end{code} % \begin{macrocode} function nodex.node_id(n) return string.gsub(tostring(n), '^ 1 then out = out .. tpl.type_id(n.subtype) end return out else return tostring(n.subtype) end assert(false) end % \end{macrocode} % % \subsubsection{tpl --- Template function} % % \begin{macrocode} function tpl.round(number) local mult = 10^(options.decimalplaces or 0) return math.floor(number * mult + 0.5) / mult end % \end{macrocode} % % \begin{macrocode} function tpl.length(input) input = tonumber(input) input = input / tex.sp('1' .. options.unit) return string.format('%g%s', tpl.round(input), options.unit) end % \end{macrocode} % % \begin{macrocode} function tpl.fill(number, order, field) if order ~= nil and order ~= 0 then if field == 'stretch' then out = '+' else out = '-' end return out .. string.format( '%gfi%s', number / 2^16, string.rep('l', order - 1) ) else return tpl.length(number) end end % \end{macrocode} % % \begin{macrocode} tpl.node_colors = { hlist = {'red', 'bright'}, vlist = {'green', 'bright'}, rule = {'blue', 'bright'}, ins = {'blue'}, mark = {'magenta'}, adjust = {'cyan'}, boundary = {'red', 'bright'}, disc = {'green', 'bright'}, whatsit = {'yellow', 'bright'}, local_par = {'blue', 'bright'}, dir = {'magenta', 'bright'}, math = {'cyan', 'bright'}, glue = {'magenta', 'bright'}, kern = {'green', 'bright'}, penalty = {'yellow', 'bright'}, unset = {'blue'}, style = {'magenta'}, choice = {'cyan'}, noad = {'red'}, radical = {'green'}, fraction = {'yellow'}, accent = {'blue'}, fence = {'magenta'}, math_char = {'cyan'}, sub_box = {'red', 'bright'}, sub_mlist = {'green', 'bright'}, math_text_char = {'yellow', 'bright'}, delim = {'blue', 'bright'}, margin_kern = {'magenta', 'bright'}, glyph = {'cyan', 'bright'}, align_record = {'red'}, pseudo_file = {'green'}, pseudo_line = {'yellow'}, page_insert = {'blue'}, split_insert = {'magenta'}, expr_stack = {'cyan'}, nested_list = {'red'}, span = {'green'}, attribute = {'yellow'}, glue_spec = {'magenta'}, attribute_list = {'cyan'}, temp = {'magenta'}, align_stack = {'red', 'bright'}, movement_stack = {'green', 'bright'}, if_stack = {'yellow', 'bright'}, unhyphenated = {'magenta', 'bright'}, hyphenated = {'cyan', 'bright'}, delta = {'red'}, passive = {'green'}, shape = {'yellow'}, } % \end{macrocode} % % \begin{macrocode} function tpl.color_code(code) return string.char(27) .. '[' .. tostring(code) .. 'm' end % \end{macrocode} % % \begin{code} % local colors = { % -- attributes % reset = 0, % clear = 0, % bright = 1, % dim = 2, % underscore = 4, % blink = 5, % reverse = 7, % hidden = 8, % % -- foreground % black = 30, % red = 31, % green = 32, % yellow = 33, % blue = 34, % magenta = 35, % cyan = 36, % white = 37, % % -- background % onblack = 40, % onred = 41, % ongreen = 42, % onyellow = 43, % onblue = 44, % onmagenta = 45, % oncyan = 46, % onwhite = 47, % } % \end{code} % \begin{macrocode} function tpl.color(color, mode, background) if options.color ~= 'colored' then return '' end % \end{macrocode} % % \begin{macrocode} local out = '' local code = '' % \end{macrocode} % % \begin{macrocode} if mode == 'bright' then out = tpl.color_code(1) elseif mode == 'dim' then out = tpl.color_code(2) end % \end{macrocode} % % \begin{macrocode} if not background then if color == 'reset' then code = 0 elseif color == 'red' then code = 31 elseif color == 'green' then code = 32 elseif color == 'yellow' then code = 33 elseif color == 'blue' then code = 34 elseif color == 'magenta' then code = 35 elseif color == 'cyan' then code = 36 else code = 37 end else if color == 'black' then code = 40 elseif color == 'red' then code = 41 elseif color == 'green' then code = 42 elseif color == 'yellow' then code = 43 elseif color == 'blue' then code = 44 elseif color == 'magenta' then code = 45 elseif color == 'cyan' then code = 46 elseif color == 'white' then code = 47 else code = 40 end end return out .. tpl.color_code(code) end % \end{macrocode} % % \begin{macrocode} function tpl.key_value(key, value) local out = tpl.color('yellow') .. key .. ': ' if value then out = out .. tpl.color('white') .. value .. '; ' end return out .. tpl.color('reset') end % \end{macrocode} % % \begin{macrocode} function tpl.char(input) return string.format('%q', unicode.utf8.char(input)) end % \end{macrocode} % % \begin{macrocode} function tpl.type(type, id) local out = tpl.color( tpl.node_colors[type][1], tpl.node_colors[type][2] ) .. string.upper(type) if options.verbosity > 1 then out = out .. tpl.type_id(id) end return out .. tpl.color('reset') .. ' ' end % \end{macrocode} % % \begin{macrocode} function tpl.callback_variable(variable_name, variable) if variable ~= nil and variable ~= '' then tpl.print(variable_name .. ': ' .. tostring(variable)) end end % \end{macrocode} % % \begin{macrocode} function tpl.line(length) if length == 'long' then return '------------------------------------------' else return '-----------------------' end end % \end{macrocode} % % \begin{macrocode} function tpl.callback(callback_name, variables) tpl.print('\n\n') tpl.print('Callback: ' .. tpl.color('red', '', true) .. callback_name .. tpl.color('reset') ) if variables then for name, value in pairs(variables) do if value ~= nil and value ~= '' then tpl.print(' - ' .. name .. ': ' .. tostring(value)) end end end tpl.print(tpl.line('long')) end % \end{macrocode} % % \begin{macrocode} function tpl.type_id(id) return '[' .. tostring(id) .. ']' end % \end{macrocode} % % \begin{macrocode} function tpl.branch(connection_type, connection_state, last) local c = connection_type local s = connection_state local l = last if c == 'list' and s == 'stop' and l == false then return ' ' elseif c == 'field' and s == 'stop' and l == false then return ' ' elseif c == 'list' and s == 'continue' and l == false then return '│ ' elseif c == 'field' and s == 'continue' and l == false then return '║ ' elseif c == 'list' and s == 'continue' and l == true then return '├─' elseif c == 'field' and s == 'continue' and l == true then return '╠═' elseif c == 'list' and s == 'stop' and l == true then return '└─' elseif c == 'field' and s == 'stop' and l == true then return '╚═' end end % \end{macrocode} % % \begin{macrocode} function tpl.branches(level, connection_type) local out = '' for i = 1, level - 1 do out = out .. tpl.branch('list', tree.state[i]['list'], false) out = out .. tpl.branch('field', tree.state[i]['field'], false) end % \end{macrocode} % Format the last branches % \begin{macrocode} if connection_type == 'list' then out = out .. tpl.branch('list', tree.state[level]['list'], true) else out = out .. tpl.branch('list', tree.state[level]['list'], false) out = out .. tpl.branch('field', tree.state[level]['field'], true) end return out end % \end{macrocode} % % \begin{macrocode} function tpl.print(text) if options.channel == 'log' then if not log then log = io.open(tex.jobname .. '_nodetree.log', 'a') end log:write(text, '\n') else print(' ' .. text) end end % \end{macrocode} % % \subsubsection{tree --- Build the node tree} % % \begin{macrocode} function tree.format_field(head, field) local out = '' % \end{macrocode} % % \begin{macrocode} if not head[field] or head[field] == 0 then return '' end % \end{macrocode} % % \begin{macrocode} if options.verbosity < 2 and -- glyph field == 'font' or field == 'left' or field == 'right' or field == 'uchyph' or -- hlist field == 'dir' or field == 'glue_order' or field == 'glue_sign' or field == 'glue_set' or -- glue field == 'stretch_order' then return '' elseif options.verbosity < 3 and field == 'prev' or field == 'next' or field == 'id' then return '' end % \end{macrocode} % % \begin{macrocode} if field == 'prev' or field == 'next' then out = nodex.node_id(head[field]) elseif field == 'subtype' then out = nodex.subtype(head) elseif field == 'width' or field == 'height' or field == 'depth' or field == 'kern' or field == 'shift' then out = tpl.length(head[field]) elseif field == 'char' then out = tpl.char(head[field]) elseif field == 'glue_set' then out = tpl.round(head[field]) elseif field == 'stretch' or field == 'shrink' then out = tpl.fill(head[field], head[field .. '_order'], field) else out = tostring(head[field]) end % \end{macrocode} % % \begin{macrocode} return tpl.key_value(field, out) end % \end{macrocode} % % |level| is a integer beginning with 1. The variable |connection_type| % is a string, which can be either |list| or |field|. The variable % |connection_state| is a string, which can be either |continue| or % |stop|. % \begin{macrocode} function tree.set_state(level, connection_type, connection_state) if not tree.state[level] then tree.state[level] = {} end tree.state[level][connection_type] = connection_state end % \end{macrocode} % % \begin{macrocode} function tree.analyze_fields(fields, level) local max = 0 local connection_state = '' for _ in pairs(fields) do max = max + 1 end local count = 0 for field_name, recursion_node in pairs(fields) do count = count + 1 if count == max then connection_state = 'stop' else connection_state = 'continue' end tree.set_state(level, 'field', connection_state) tpl.print(tpl.branches(level, 'field') .. tpl.key_value(field_name)) tree.analyze_list(recursion_node, level + 1) end end % \end{macrocode} % % \begin{macrocode} function tree.analyze_node(head, level) local connection_state local out = '' if head.next then connection_state = 'continue' else connection_state = 'stop' end tree.set_state(level, 'list', connection_state) out = tpl.branches(level, 'list') .. tpl.type(node.type(head.id), head.id) if options.verbosity > 1 then out = out .. tpl.key_value('no', nodex.node_id(head)) end % \end{macrocode} % % \begin{macrocode} local fields = {} for field_id, field_name in pairs(node.fields(head.id, head.subtype)) do if field_name ~= 'next' and field_name ~= 'prev' and node.is_node(head[field_name]) then fields[field_name] = head[field_name] else out = out .. tree.format_field(head, field_name) end end % \end{macrocode} % % \begin{macrocode} tpl.print(out) tree.analyze_fields(fields, level) end % \end{macrocode} % % \begin{macrocode} function tree.analyze_list(head, level) while head do tree.analyze_node(head, level) head = head.next end end % \end{macrocode} % % \begin{macrocode} function tree.analyze_callback(head) tree.analyze_list(head, 1) tpl.print(tpl.line('short') .. '\n') end % \end{macrocode} % % \subsubsection{callbacks --- Callback wrapper} % % \begin{macrocode} function callbacks.contribute_filter(extrainfo) tpl.callback('contribute_filter', {extrainfo = extrainfo}) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.buildpage_filter(extrainfo) tpl.callback('buildpage_filter', {extrainfo = extrainfo}) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.pre_linebreak_filter(head, groupcode) tpl.callback('pre_linebreak_filter', {groupcode = groupcode}) tree.analyze_callback(head) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.linebreak_filter(head, is_display) tpl.callback('linebreak_filter', {is_display = is_display}) tree.analyze_callback(head) return true end % \end{macrocode} % % TODO: Fix return values, page output % \begin{macrocode} function callbacks.append_to_vlist_filter(head, locationcode, prevdepth, mirrored) local variables = { locationcode = locationcode, prevdepth = prevdepth, mirrored = mirrored, } tpl.callback('append_to_vlist_filter', variables) tree.analyze_callback(head) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.post_linebreak_filter(head, groupcode) tpl.callback('post_linebreak_filter', {groupcode = groupcode}) tree.analyze_callback(head) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.hpack_filter(head, groupcode, size, packtype, direction, attributelist) local variables = { groupcode = groupcode, size = size, packtype = packtype, direction = direction, attributelist = attributelist, } tpl.callback('hpack_filter', variables) tree.analyze_callback(head) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.vpack_filter(head, groupcode, size, packtype, maxdepth, direction, attributelist) local variables = { groupcode = groupcode, size = size, packtype = packtype, maxdepth = tpl.length(maxdepth), direction = direction, attributelist = attributelist, } tpl.callback('vpack_filter', variables) tree.analyze_callback(head) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.hpack_quality(incident, detail, head, first, last) local variables = { incident = incident, detail = detail, first = first, last = last, } tpl.callback('hpack_quality', variables) tree.analyze_callback(head) end % \end{macrocode} % % \begin{macrocode} function callbacks.vpack_quality(incident, detail, head, first, last) local variables = { incident = incident, detail = detail, first = first, last = last, } tpl.callback('vpack_quality', variables) tree.analyze_callback(head) end % \end{macrocode} % % \begin{macrocode} function callbacks.process_rule(head, width, height) local variables = { width = width, height = height, } tpl.callback('process_rule', variables) tree.analyze_callback(head) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.pre_output_filter(head, groupcode, size, packtype, maxdepth, direction) local variables = { groupcode = groupcode, size = size, packtype = packtype, maxdepth = maxdepth, direction = direction, } tpl.callback('pre_output_filter', variables) tree.analyze_callback(head) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.hyphenate(head, tail) tpl.callback('hyphenate') tpl.print('head:') tree.analyze_callback(head) tpl.print('tail:') tree.analyze_callback(tail) end % \end{macrocode} % % \begin{macrocode} function callbacks.ligaturing(head, tail) tpl.callback('ligaturing') tpl.print('head:') tree.analyze_callback(head) tpl.print('tail:') tree.analyze_callback(tail) end % \end{macrocode} % % \begin{macrocode} function callbacks.kerning(head, tail) tpl.callback('kerning') tpl.print('head:') tree.analyze_callback(head) tpl.print('tail:') tree.analyze_callback(tail) end % \end{macrocode} % % \begin{macrocode} function callbacks.insert_local_par(local_par, location) tpl.callback('insert_local_par', {location = location}) tree.analyze_callback(local_par) return true end % \end{macrocode} % % \begin{macrocode} function callbacks.mlist_to_hlist(head, display_type, need_penalties) local variables = { display_type = display_type, need_penalties = need_penalties, } tpl.callback('mlist_to_hlist', variables) tree.analyze_callback(head) return node.mlist_to_hlist(head, display_type, need_penalties) end % \end{macrocode} % % \subsubsection{base --- Exported base functions} % % \begin{macrocode} function base.normalize_options() options.verbosity = tonumber(options.verbosity) options.decimalplaces = tonumber(options.decimalplaces) end % \end{macrocode} % % \begin{macrocode} function base.set_default_options() local defaults = { verbosity = 1, callback = 'postlinebreak', engine = 'luatex', color = 'colored', decimalplaces = 2, unit = 'pt', channel = 'term', } if not options then options = {} end for key, value in pairs(defaults) do if not options[key] then options[key] = value end end base.normalize_options() end % \end{macrocode} % % \begin{macrocode} function base.set_option(key, value) if not options then options = {} end options[key] = value base.normalize_options() end % \end{macrocode} % % \begin{macrocode} function base.get_option(key) if not options then options = {} end if options[key] then return options[key] end end % \end{macrocode} % % \begin{macrocode} function base.get_callback_name(alias) if alias == 'contribute' or alias == 'contributefilter' then return 'contribute_filter' % \end{macrocode} % % \begin{macrocode} elseif alias == 'buildpage' or alias == 'buildpagefilter' then return 'buildpage_filter' % \end{macrocode} % % \begin{macrocode} elseif alias == 'preline' or alias == 'prelinebreakfilter' then return 'pre_linebreak_filter' % \end{macrocode} % % \begin{macrocode} elseif alias == 'line' or alias == 'linebreakfilter' then return 'linebreak_filter' % \end{macrocode} % % \begin{macrocode} elseif alias == 'append' or alias == 'appendtovlistfilter' then return 'append_to_vlist_filter' % \end{macrocode} % % \begin{macrocode} elseif alias == 'postline' or alias == 'postlinebreakfilter' then return 'post_linebreak_filter' % \end{macrocode} % % \begin{macrocode} elseif alias == 'hpack' or alias == 'hpackfilter' then return 'hpack_filter' % \end{macrocode} % % \begin{macrocode} elseif alias == 'vpack' or alias == 'vpackfilter' then return 'vpack_filter' % \end{macrocode} % TODO: Fix: Unable to register callback % \begin{macrocode} elseif alias == 'hpackq' or alias == 'hpackquality' then return 'hpack_quality' % \end{macrocode} % TODO: Fix: Unable to register callback % \begin{macrocode} elseif alias == 'vpackq' or alias == 'vpackquality' then return 'vpack_quality' % \end{macrocode} % % \begin{macrocode} elseif alias == 'process' or alias == 'processrule' then return 'process_rule' % \end{macrocode} % % \begin{macrocode} elseif alias == 'preout' or alias == 'preoutputfilter' then return 'pre_output_filter' % \end{macrocode} % % \begin{macrocode} elseif alias == 'hyph' or alias == 'hyphenate' then return 'hyphenate' % \end{macrocode} % % \begin{macrocode} elseif alias == 'liga' or alias == 'ligaturing' then return 'ligaturing' % \end{macrocode} % % \begin{macrocode} elseif alias == 'kern' or alias == 'kerning' then return 'kerning' % \end{macrocode} % % \begin{macrocode} elseif alias == 'insert' or alias == 'insertlocalpar' then return 'insert_local_par' % \end{macrocode} % % \begin{macrocode} elseif alias == 'mhlist' or alias == 'mlisttohlist' then return 'mlist_to_hlist' % \end{macrocode} % % \begin{macrocode} else return 'post_linebreak_filter' end end % \end{macrocode} % % \begin{macrocode} function base.register(cb) if options.engine == 'lualatex' then luatexbase.add_to_callback(cb, callbacks[cb], 'nodetree') else id, error = callback.register(cb, callbacks[cb]) end end % \end{macrocode} % % \begin{macrocode} function base.register_callbacks() for alias in string.gmatch(options.callback, '([^,]+)') do base.register(base.get_callback_name(alias)) end end % \end{macrocode} % % \begin{macrocode} function base.unregister(cb) if options.engine == 'lualatex' then luatexbase.remove_from_callback(cb, 'nodetree') else id, error = callback.register(cb, nil) end end % \end{macrocode} % % \begin{macrocode} function base.unregister_callbacks() for alias in string.gmatch(options.callback, '([^,]+)') do base.unregister(base.get_callback_name(alias)) end end % \end{macrocode} % % \begin{macrocode} function base.execute() local c = base.get_callback() if options.engine == 'lualatex' then luatexbase.add_to_callback(c, callbacks.post_linebreak_filter, 'nodetree') else id, error = callback.register(c, callbacks.post_linebreak_filter) end end % \end{macrocode} % % \begin{macrocode} function base.analyze(head) tpl.print('\n') tree.analyze_list(head, 1) end % \end{macrocode} % % \begin{macrocode} return base % \end{macrocode} % \iffalse % % \fi % % \Finale \endinput