summaryrefslogtreecommitdiff
path: root/macros/luatex/latex
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2024-03-26 03:03:59 +0000
committerNorbert Preining <norbert@preining.info>2024-03-26 03:03:59 +0000
commit904f56f2c26edc7eb11d5a27579bccbeca29c618 (patch)
tree8a4d6d2e163e93dd2c13a28db996b419aee856b4 /macros/luatex/latex
parent05f8146ef2eea2cd71b9c96583c6d893e6c76af5 (diff)
CTAN sync 202403260303
Diffstat (limited to 'macros/luatex/latex')
-rw-r--r--macros/luatex/latex/longmath/README45
-rw-r--r--macros/luatex/latex/longmath/longmath-doc.cls168
-rw-r--r--macros/luatex/latex/longmath/longmath-doc.lua192
-rw-r--r--macros/luatex/latex/longmath/longmath-doc.pdfbin0 -> 175384 bytes
-rw-r--r--macros/luatex/latex/longmath/longmath-doc.tex976
-rw-r--r--macros/luatex/latex/longmath/longmath.lua428
-rw-r--r--macros/luatex/latex/longmath/longmath.sty223
7 files changed, 2032 insertions, 0 deletions
diff --git a/macros/luatex/latex/longmath/README b/macros/luatex/latex/longmath/README
new file mode 100644
index 0000000000..da1e522891
--- /dev/null
+++ b/macros/luatex/latex/longmath/README
@@ -0,0 +1,45 @@
+
+longmath
+--------
+
+Nested delimiter groups extending over multiple array cells or lines.
+
+The longmath package provides yet another solution to some well known
+typesetting problems solved in a variety of ways: multi line formulas
+with paired and nested delimiters. It tackles the problem at the Lua
+level, which has some advantages over solutions implemented in TeX.
+In particular, the TeX code need not be executed multiple times, and
+there is no interference between TeX grouping and the nesting of
+delimiter groups.
+
+As a byproduct, delimiters can be scaled in various ways, inner
+delimiters come in different flavours like relational and binary
+operators, punctuation symbols etc., and outer delimiters can be
+selected automatically according to the nesting level. Last but not
+least, delimiter groups can even extend across several array cells
+or across the whole document.
+
+A special environment is provided as well, which allows multi line
+expressions to be placed inside a displayed equation and make TeX
+do the line splitting and alignment.
+
+This is the first and in parts still experimental version. If anyone
+actually uses it, the author would be happy to receive bug reports,
+critics and suggestions.
+
+(c) 2024 Hans-Jürgen Matschull [ hjm.tex@matschull.net ]
+
+This work may be distributed and/or modified under the conditions
+of the LaTeX Project Public License version 1.3 or later.
+
+The following files belong to the longmath package version 0.1:
+
+package and documentation: longmath.sty longmath.lua longmath-doc.pdf
+
+documentation source: longmath-doc.tex longmath-doc.cls longmath-doc.lua
+
+
+
+
+
+
diff --git a/macros/luatex/latex/longmath/longmath-doc.cls b/macros/luatex/latex/longmath/longmath-doc.cls
new file mode 100644
index 0000000000..04ce8b2e3d
--- /dev/null
+++ b/macros/luatex/latex/longmath/longmath-doc.cls
@@ -0,0 +1,168 @@
+%%
+%% longmath-doc.cls is part of longmath version 0.1.
+%%
+%% (c) 2024 Hans-Jürgen Matschull
+%%
+%% 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 Hans-Jürgen Matschull
+%%
+%% see README for a list of files belonging to longmath.
+%%
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesClass{longmath-doc}[2024/03/25]
+\LoadClass[a4paper,10pt]{scrartcl}
+\RequirePackage{geometry}
+\geometry{hmargin=20mm,vmargin={20mm,30mm}}
+\RequirePackage{tikz}
+
+\let\codefont\normalsize
+\let\execfont\normalsize
+
+\definecolor{pcol}{rgb}{0.1 0.0 0.3}
+\definecolor{pcol}{rgb}{0.1 0.0 0.3}
+
+\RequirePackage[no-math]{fontspec}
+\setmainfont{Linux Libertine}
+\setmonofont[Scale=0.80]{Cascadia Mono PL}
+\setsansfont[Scale=0.85]{Droid Sans}
+
+
+\RequirePackage{unicode-math}
+\setmathfont{Tex Gyre Pagella Math}
+
+\RequirePackage{amsmath}
+
+\def\p#1{\textcolor{pcol}{\textsf{#1}}}
+
+\definecolor{code}{rgb}{0.4 0.6 0.8}
+\definecolor{exec}{rgb}{0.8 0.6 0.3}
+
+\colorlet{codeback}{code!02!white}
+\colorlet{execback}{exec!03!white}
+\colorlet{whiteback}{white}
+
+\colorlet{codebord}{code!50!gray}
+\colorlet{execbord}{exec!50!gray}
+\colorlet{whitebord}{white}
+
+\definecolor{codetext}{rgb}{0.0 0.0 0.1}
+\definecolor{codeword}{rgb}{0.2 0.0 0.2}
+\definecolor{codedefn}{rgb}{0.1 0.2 0.1}
+\definecolor{codenumb}{rgb}{0.1 0.0 0.3}
+\definecolor{codepunc}{rgb}{0.4 0.2 0.0}
+\definecolor{codestring}{rgb}{0.4 0.0 0.0}
+\definecolor{codekey}{rgb}{0.5 0.3 0.0}
+\definecolor{codemacro}{rgb}{0.2 0.0 0.2}
+\definecolor{codeluac}{rgb}{0.3 0.0 0.1}
+\definecolor{codemath}{rgb}{0.4 0.2 0.0}
+\definecolor{codeoper}{rgb}{0.2 0.0 0.2}
+\definecolor{codegroup}{rgb}{0.6 0.3 0.0}
+\definecolor{coderest}{rgb}{0.9 0.0 0.9}
+\definecolor{codespace}{rgb}{0.0 0.0 0.0}
+\definecolor{codecomm}{rgb}{0.3 0.3 0.3}
+
+\newcatcodetable\longmathdoc@cctable@
+\savecatcodetable\longmathdoc@cctable@
+\newcount\longmathdoc@ccnum@
+\longmathdoc@ccnum@=\the\longmathdoc@cctable@
+
+\setbox0\hbox{\codefont\texttt{0}}
+\newdimen\ttwidth\ttwidth=\wd0
+
+\directlua{ longmathdoc = require 'longmath-doc.lua' }
+
+\newsavebox{\codebox}
+\newsavebox{\execbox}
+\newdimen\boxsize
+\newdimen\codesize
+\newdimen\execsize
+\newdimen\halfsize
+\newdimen\fullsize
+
+\newif\ifcode
+
+\def\skipin{\ifhmode\par\fi\ifdim\prevdepth>\dp\strutbox\relax\vskip-0.5\fboxsep\fi}
+\def\skipex{\ifhmode\par\fi\ifdim\prevdepth>\dp\strutbox\relax\vskip 1.0\fboxsep\fi}
+
+\def\xpar{\vskip 0.5\fboxsep\par}
+
+\fboxsep = 1.1mm
+
+\def\codeframe#1{\fcolorbox{#1bord}{#1back}{\box\codebox}}
+\def\execframe#1{\fcolorbox{#1bord}{#1back}{\box\execbox}}
+
+\def\.{\strut\\}
+\def\:{\strut\\[-1.9\baselineskip]}
+
+\def\startcode#1{\begingroup\let\do\@makeother\dospecials\@makeother\"\directlua{longmathdoc.readcode('#1')}}
+\def\startexec{\begin{lrbox}{\execbox}\begin{minipage}[t]{\execsize}
+\execfont\abovedisplayskip 5pt \abovedisplayshortskip 5pt }
+\def\stopexec{\end{minipage}\end{lrbox}\dp\execbox = \dimexpr \dp\execbox + 2pt \relax }
+
+\def\makecodebox{\begin{lrbox}{\codebox}\begin{minipage}[t]{\codesize}\codefont\ttfamily
+ \directlua{longmathdoc.writecode()}\end{minipage}\end{lrbox}}
+
+\newenvironment{code*}
+{\xpar\startcode{code*}}
+{\codesize = \dimexpr \linewidth - 2 \fboxrule - 2 \fboxsep \relax
+ \makecodebox\skipin\par\codeframe{code}\xpar}
+
+\newenvironment{code+}
+{\par\startcode{code+}}
+{\global\codetrue \global\codesize = \codesize }
+
+\newenvironment{code}
+{\par\startcode{code}}
+{\halfsize = \dimexpr 0.5 \linewidth - 2 \fboxrule - 2 \fboxsep - 0.5 \parskip \relax
+ \global\codetrue \ifdim \codesize < \halfsize \relax \global\codesize = \halfsize \fi}
+
+\def\makesize{
+ \ifcode
+ \execsize = \dimexpr \linewidth - 4 \fboxrule - 4 \fboxsep - 1.2 \parskip - \codesize \relax
+ \else
+ \execsize = \dimexpr \linewidth - 2 \fboxrule - 2 \fboxsep \relax
+ \fi
+}
+
+\newenvironment{exec}
+{\makesize\makecodebox\startexec\raggedright}
+{\stopexec\skipin\ifcode\doublebox{code}{exec}\else\xpar\execframe{exec}\fi\global\codefalse\xpar}
+
+\newenvironment{exec*}
+{\makesize\makecodebox\startexec}
+{\stopexec\skipin\ifcode\doublebox{code}{exec}\else\xpar\execframe{exec}\fi\global\codefalse\xpar}
+
+\def\doublebox#1#2{%
+ \ifdim \ht\codebox>\ht\execbox \ht\execbox=\ht\codebox \else \ht\codebox=\ht\execbox \fi
+ \ifdim \dp\codebox>\dp\execbox \dp\execbox=\dp\codebox \else \dp\codebox=\dp\execbox \fi
+ \xpar\codeframe{#1}\hfill\execframe{#2}
+}
+
+\def\ecomm{\quad}
+\def\bcomm{\egroup}
+\begingroup\catcode`\{=\active\global\let{\bcomm\endgroup
+\def\comm{\par\skipex\noindent $\bullet$ \bgroup\aftergroup\ecomm\bgroup\catcode`\{=\active}
+
+\begingroup
+\catcode`"\active
+\gdef\verbA{\begingroup\let\do\@makeother\dospecials\let"\verbB\directlua{ longmathdoc.readstring() }}
+\gdef\verbB{\endgroup\directlua{ longmathdoc.writestring() }}
+\gdef"{\verbA}
+\endgroup
+
+\def\verbC#1{\texttt{\textcolor{codetext!50!black}{#1}}}
+
+\catcode`°\active \def°#1°{\textsl{#1}}
+
+\AtBeginDocument{\catcode`"\active}
+
+\endinput
diff --git a/macros/luatex/latex/longmath/longmath-doc.lua b/macros/luatex/latex/longmath/longmath-doc.lua
new file mode 100644
index 0000000000..e334cc2e57
--- /dev/null
+++ b/macros/luatex/latex/longmath/longmath-doc.lua
@@ -0,0 +1,192 @@
+--
+-- longmath-doc.lua is part of longmath version 0.1.
+--
+-- (c) 2024 Hans-Jürgen Matschull
+--
+-- 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 Hans-Jürgen Matschull
+--
+-- see README for a list of files belonging to longmath.
+--
+
+local match = unicode.utf8.match
+local gsub = unicode.utf8.gsub
+local find = unicode.utf8.find
+
+local nixpatt = {
+ { n = 'newl', p = '\n' },
+ { n = 'text', p = '[^\n]*' },
+}
+
+local texpatt = {
+ { n = 'group', p = '%b{}' },
+ { n = 'math', p = '%(%)' },
+ { n = 'defn', p = '\\newcount\\[%w@]+' },
+ { n = 'defn', p = '\\newdimen\\[%w@]+' },
+ { n = 'defn', p = '\\def\\[%w@]+' },
+ { n = 'expl', p = '%%%%%%[^\n]*' },
+ { n = 'expl', p = '%&%&%&[^\n]*' },
+ { n = 'comm', p = '%%[^\n]*' },
+ { n = 'oper', p = '[#]+%d' },
+ { n = 'macro', p = '\\[%w@]+' },
+ { n = 'punc', p = '\\%W' },
+ { n = 'newl', p = '\n' },
+ { n = 'space', p = '[ ]+' },
+ { n = 'inter', p = "»[^«]*«" },
+ { n = 'text', p = '[%w@_….,;:~?!\'€∞]+' },
+ { n = 'math', p = '[-+/^=≠<>≤≥·×÷≈"]+' },
+ { n = 'open', p = '[%[%(%{]' },
+ { n = 'close', p = '[%]%)%}]' },
+ { n = 'punc', p = '[%%&$#*|`\']' },
+}
+
+
+local function write( item, d )
+ if item.n == 'open' then item.n = 'group' if d < 9 then d = d + 1 end end
+ if item.n == 'close' then if d > 0 then d = d - 1 end item.n = 'group' end
+ if item.n == 'newl' then
+ tex.sprint( '\\\\\\strut' )
+ elseif item.n == 'expl' then
+ local t = item.p
+ t = gsub( t, '%%%%%%','' )
+ t = gsub( t, '%-%-%-', '' )
+ t = gsub( t, '%&%&%&', '' )
+ tex.sprint( '\\rlap{\\textrm{'..t..'}}' )
+ elseif item.n == 'inter' then
+ tex.sprint( ( item.p:gsub( '«', '' ):gsub( '»', '' ) ) )
+ else
+ local ex = string.explode( item.p, ' ' )
+ tex.sprint( '\\textcolor{code' .. item.n .. '}{' )
+ for i, t in ipairs( ex ) do
+ if i > 1 then tex.sprint( '{ }' ) end
+ t = t:gsub( '\\%<', '\\{' ):gsub( '\\%>', '\\}' ):gsub( '«', '' ):gsub( '»', '' )
+ tex.write( t )
+ end
+ tex.sprint( '}' )
+ end
+ return d
+end
+
+local function nsplit( code )
+ local list1 = {}
+ while code ~= '' do
+ local a, b
+ for _, p in ipairs( nixpatt ) do
+ a, b = match( code, '^(' .. p.p .. ')(.*)$' )
+ if a and b then table.insert( list1, { n = p.n, p = a } ); code = b; break end
+ end
+ if not a then table.insert( list1, { n = 'rest', p = code } ); code = '' end
+ end
+ return list1
+end
+
+local function tsplit( code )
+ local list1 = {}
+ while code ~= '' do
+ local a, b
+ for _, p in ipairs( texpatt ) do
+ a, b = match( code, '^(' .. p.p .. ')(.*)$' )
+ if a and b then table.insert( list1, { n = p.n, p = a } ); code = b; break end
+ end
+ if not a then table.insert( list1, { n = 'rest', p = code } ); code = '' end
+ end
+ local list2 = {}
+ local lgrp = 0
+ local isub
+ for _, item in ipairs( list1 ) do
+ if item.n == 'luap' then
+ table.insert( list2, { n = 'macro', p = '\\begin' } )
+ table.insert( list2, { n = 'open', p = '{' } )
+ table.insert( list2, { n = 'luac', p = 'luap' } )
+ table.insert( list2, { n = 'close', p = '}' } )
+ isub = lsplit( item.p:gsub( '\\begin{luap}', '' ):gsub( '\\end{luap}', '' ) )
+ for _, i in ipairs( isub ) do table.insert( list2, i ) end
+ table.insert( list2, { n = 'macro', p = '\\end' } )
+ table.insert( list2, { n = 'open', p = '{' } )
+ table.insert( list2, { n = 'luac', p = 'luap' } )
+ table.insert( list2, { n = 'close', p = '}' } )
+ elseif item.n == 'group' then
+ local a, b = item.p:sub( 1, 1 ), item.p:sub( -1, -1 )
+ if lgrp > 0 then
+ item = lsplit( item.p:sub( 2, -2 ) )
+ lgrp = lgrp - 1
+ else
+ item = tsplit( item.p:sub( 2, -2 ) )
+ end
+ table.insert( list2, { n = 'open', p = a } )
+ for _, i in ipairs( item ) do table.insert( list2, i ) end
+ table.insert( list2, { n = 'close', p = b } )
+ elseif item.n == 'macro' then
+ local lc = item.p:sub( 2 )
+ table.insert( list2, item )
+ elseif item.n == 'space' then
+ table.insert( list2, item )
+ else
+ table.insert( list2, item )
+ end
+ end
+ return list2
+end
+
+local codelist = {}
+
+local function readcode( env )
+ local name = string.format( '\\end{%s}', env )
+ local patt = name:gsub( '(%W)', '%%%1' )
+ local typ, line
+ local lines, len, spc = {}, 0, 1000
+ while true do
+ line = token.scan_word()
+ if type( line ) == 'string' then
+ local a, b = line:find( patt )
+ if a and b then break end
+ line = line:gsub( '\\%.[%[%s%w%]]*$', '' ):gsub( '\\%.', '\\\\' )
+ line = line:gsub( '%s*$', '' )
+ local stub = line:gsub( '%%%%%%.*$', '' ):gsub( '%-%-%-.*$', '' )
+ if not typ and stub:match( '[^\\]\\%w' ) then typ = tsplit end
+ len = math.max( len, unicode.utf8.len( stub:gsub( '»[^«]*«', '' ) ) )
+ spc = math.min( spc, line:find( '%S' ) or 1000 )
+ table.insert( lines, line )
+ else
+ token.get_next()
+ table.insert( lines, '' )
+ end
+ end
+ local code = ''
+ for _, line in ipairs( lines ) do code = code .. line:sub( spc ) .. '\n' end
+ code = code:gsub( '\\(%W)', '°%1' ):gsub( '°}', '°>' ):gsub( '°{', '°<' ):gsub( '°', '\\' ):gsub( '\n$', '' )
+ tex.setdimen( 'global', 'codesize', ( len - spc + 1.5 ) * tex.dimen.ttwidth )
+ codelist = ( typ or lsplit )( code )
+ tex.sprint( tex.count['longmathdoc@ccnum@'], '\\endgroup' )
+ tex.sprint( name )
+end
+
+local function writecode()
+ local dep = 0
+ tex.sprint( '\\strut' )
+ for _, item in ipairs( codelist ) do dep = write( item, dep ) end
+end
+
+local dummy
+local function readstring()
+ dummy = string.explode( token.scan_string() )
+end
+local function writestring()
+ for i, c in ipairs( dummy ) do
+ if i > 1 then tex.sprint( '~' ) end
+ tex.sprint( '\\verbC{' ) tex.write( c ) tex.sprint( '}' )
+ end
+end
+
+return { readcode = readcode, writecode = writecode,
+ readstring = readstring, writestring = writestring }
+
diff --git a/macros/luatex/latex/longmath/longmath-doc.pdf b/macros/luatex/latex/longmath/longmath-doc.pdf
new file mode 100644
index 0000000000..2d82bd0a3f
--- /dev/null
+++ b/macros/luatex/latex/longmath/longmath-doc.pdf
Binary files differ
diff --git a/macros/luatex/latex/longmath/longmath-doc.tex b/macros/luatex/latex/longmath/longmath-doc.tex
new file mode 100644
index 0000000000..8e64064d87
--- /dev/null
+++ b/macros/luatex/latex/longmath/longmath-doc.tex
@@ -0,0 +1,976 @@
+%%
+%% longmath-doc.tex is part of longmath version 0.1.
+%%
+%% (c) 2024 Hans-Jürgen Matschull
+%%
+%% 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 Hans-Jürgen Matschull
+%%
+%% see README for a list of files belonging to longmath.
+%%
+
+\documentclass{longmath-doc}
+
+\usepackage{longmath}
+\usepackage{unicode-math}
+\usepackage{tikz}
+\usepackage{array}
+\usepackage{multido}
+
+
+\def\intro{
+The \p{longmath} package provides yet another solution to some well known
+typesetting problems solved in a variety of ways: multi line formulas
+with paired and nested delimiters. It tackles the problem at the Lua
+level, which has some advantages over solutions implemented in \TeX.
+In particular, the \TeX\ code need not be executed multiple times, and
+there is no interference between \TeX\ grouping and the nesting of
+delimiter groups.
+
+As a byproduct, delimiters can be scaled in various ways, inner
+delimiters come in different flavours like relational and binary
+operators, punctuation symbols etc., and outer delimiters can be
+selected automatically according to the nesting level. Last but not
+least, delimiter groups can even extend across several array cells
+or across the whole document.
+
+A special environment is provided as well, which allows multi line
+expressions to be placed inside a displayed equation and make \TeX\
+do the line splitting and alignment.
+
+This is the first and in parts still experimental version. If anyone
+actually uses it, the author would be happy to receive bug reports,
+critics and suggestions.
+}
+
+\newcommand{\icol}[1]{% inline column vector
+ \lleft(\begin{matrix}#1\end{matrix}\rright)%
+}
+
+\def\tr{\mathrm{Tr}}
+
+\def\lagrange{
+ -\frac{1}{4}
+ \lleft( \partial_\mu B_\nu-\partial_\nu B_\mu \rright )
+ \lleft( \partial^\mu B^\nu-\partial^\nu B^\mu \rright )
+ - \frac{1}{8}\tr\lleft\{
+\lleft[ \partial_\mu W_\nu-\partial_\nu W_\mu+\frac{ig_2}{2}
+ \lleft( W_\mu W_\nu- W_\nu W_\mu\rright) \rright]
+\lleft[ \partial^\mu W^\nu-\partial^\nu W^\mu+\frac{ig_2}{2}
+ \lleft( W^\mu W^\nu- W^\nu W^\mu\rright) \rright]
+ \rright\}
+ - \frac{1}{2}\tr\lleft\{
+ \lleft[\partial_\mu G_\nu-\partial_\nu G_\mu
+ + G_\mu G_\nu- G_\nu G_\mu\rright]
+ \lleft[\vphantom{G_\mu} \partial^\mu G^\nu-\partial^\nu G^\mu
+ + G^\mu G^\nu- G^\nu G^\mu\rright]
+ \rright\}
+ + \icol{\nu_L\\e_L}^\dagger \tilde{\sigma}^\mu i
+ \lleft[\partial_\mu-\frac{ig_1}{2}B_\mu+\frac{ig_2}{2}W_\mu\rright]\icol{\nu_L\\e_L}
+ - \lleft\{\lleft[\partial_\mu-\frac{ig_1}{2}B_\mu+\frac{ig_2}{2}W_\mu\rright]\icol{\nu_L\\e_L}
+ \rright\}^\dagger
+ i \tilde{\sigma}^\mu \icol{\nu_L\\e_L}
+ + e_R^\dagger i \sigma^\mu
+ \lleft[\partial_\mu-ig_1B_\mu\rright]e_R
+ + \nu_R^\dagger i \sigma^\mu\partial_\mu\nu_R
+ - \lleft\{ \left[\partial_\mu-ig_1B_\mu\right] e_R \rright\}^\dagger i \sigma^\mu e_R
+ - \lleft\{ \partial_\mu\nu_R \rright\}^\dagger i \sigma^\mu \nu_R
+ - \frac{\sqrt{2}}{\nu}
+ \lleft[ \icol{\nu_L\\e_L}^\dagger \phi M^ee_R
+ +e_R^\dagger M^{e\dagger} \phi^\dagger \icol{\nu_L\\e_L} \rright]
+ - \frac{\sqrt{2}}{\nu}
+ \lleft[ \icol{ -e_L\\ \nu_L}^\dagger \phi^{*}M^\nu\nu_R
+ + \nu_R^\dagger M^{\nu^\dagger} \phi^T\icol{-e_L\\\nu_L} \rright]
+ + \icol{u_L\\d_L}^\dagger\tilde{\sigma}^\mu i
+ \lleft[\partial_\mu-\frac{ig_1}{6}B_\mu+\frac{ig_2}{2}W_\mu+igG_\mu\rright]\icol{u_L\\d_L}
+ - \lleft\{\lleft[\partial_\mu-\frac{ig_1}{6}B_\mu+\frac{ig_2}{2}W_\mu+igG_\mu\rright]\icol{u_L\\d_L}\rright\}^\dagger
+ i\tilde{\sigma}^\mu \icol{u_L\\d_L}
+ + u_R^\dagger \sigma^\mu i \lleft[\partial_\mu+\frac{ig_1}{3}B_\mu+igG_\mu\rright]u_r
+ - \lleft\{ \lleft[\partial_\mu+\frac{ig_1}{3}B_\mu+igG_\mu\rright]u_r \rright\}^\dagger i \sigma^\mu u_R
+ + d_R^\dagger \sigma^\mu i \lleft[\partial_\mu - \frac{ig_1}{3}B_\mu+igG_\mu\rright]d_R
+ - \lleft\{ \lleft[\partial_\mu - \frac{ig_1}{3}B_\mu+igG_\mu\rright]d_R \rright\}^\dagger i \sigma^\mu d_R
+ - \frac{\sqrt{2}}{\nu}\lleft[\icol{u_L\\d_L}^\dagger \phi M^d d_R
+ + \lleft( M^d d_R\rright) ^\dagger \phi^\dagger \icol{u_L\\d_L}\rright]
+ -\frac{\sqrt{2}}{\nu}\lleft[\icol{-d_L\\u_L}^\dagger \phi^{*} M^u u_R
+ + \lleft( M^u u_R \rright)^\dagger \phi^\top \icol{-d_L\\u_L}\rright]
+ + \lleft\{\lleft[\partial_\mu+\frac{ig_1}{2}B_\mu+\frac{ig_2}{2}W_\mu\rright]\phi\rright\}^\dagger
+ \lleft[\partial^\mu+\frac{ig_1}{2}B^\mu+\frac{ig_2}{2}W^\mu\rright]\phi
+ - \frac{ m_h^2 }{ 2 \nu^2 } \lleft[ \phi^\dagger \phi - \frac{\nu^2}{2} \rright]^2
+}
+
+\begin{document}
+
+\begin{center}
+\tikzset{ txt/.style = { fill=white,fill opacity=0.6,text opacity=1.0 } }
+\begin{tikzpicture}
+ \useasboundingbox (0,0.53\textheight);
+ \node[color=yellow!85!black,rotate=-17,scale=1.8] at (-0.7,0)
+ {$\displaystyle \mathcal{L} = \begin{longmath}[t]{110mm+30mm} \lagrange \end{longmath}$};
+ \node[txt,scale=1.8] at (0,7.2) {\Huge\textbf{\p{longmath}}};
+ \node[txt,scale=1.2] at (0,5.4) {\Large Documentation for Version 0.1};
+ \node[txt,scale=1.2] at (0,4.0) {\large Hans-Jürgen Matschull};
+ \node[txt,scale=1.2] at (0,3.3) {\texttt{hjm.tex@matschull.net}};
+ \node[txt,scale=1.2] at (0,1.7) {\texttt{2024/03/25}};
+ \node[txt,scale=1.0] at (0,-3) {\begin{minipage}{130mm}\parskip 1.3ex \intro\end{minipage}};
+\end{tikzpicture}
+\end{center}
+
+\parskip 1ex plus 3pt
+\parindent 0pt
+\baselineskip 13pt plus 1pt
+\lineskip 2pt plus 1pt
+\lineskiplimit 2pt
+
+\newpage
+
+\section{Delimiters}
+\label{delims}
+
+The \p{longmath} package provides ``long'' versions of the "\left" and "\right" primitives.
+
+\comm{"\lleft" [°scale°] °delimiter°} marks the beginning of a delimiter group and inserts an opening °delimiter°.
+
+\comm{"\rright" [°scale°] °delimiter°} marks the end of a delimiter group and inserts a closing °delimiter°.
+
+The optional °scale° argument will be explained below. The °delimiter° can be anything that is also accepted by "\left" and "\right", including a dot representing an invisible or null delimiter, any character with a "\delcode", or an explicit "\delimiter" or "\Udelimiter". If you want to use the \p{longmath} package with existing documents, or just stick to the familiar notation, and provided that this does not cause conflicts with other packages manipulating the original primitives, it is even save to say "\let\left\lleft" and "\let\right\rright".
+\begin{code}
+ The Sellmann-Yokuzi contribution to the
+ Johansson Entropy is given by
+ $$ S^\top = k · \lleft[ A + \lleft( B_C
+ + D^{E^2} \rright) ^ { \lleft( 3+κ^2 \rright)^2 }
+ + F \rright] $$
+\end{code}
+\begin{exec}
+ The Sellmann-Yokuzi contribution to the
+ Johansson Entropy is given by
+ $$ S^\top = k · \lleft[ A +
+ \lleft( B_C + D^{E^2} \rright)^{\lleft( 3+κ^2 \rright)^2 }
+ + F \rright] $$
+\end{exec}
+The "\lleft" and "\rright" delimiters enclose a \emph{delimiter group}. Within the group, you can have arbitrarily many inner or middle delimiters. As these often have different interpretations, several ``long'' variants of the "\middle" primitive are provided, associated with different types of math atoms.
+
+\comm{"\mmiddle" [°scale°] °delimiter°} inserts an inner °delimiter° into a delimiter group.
+
+\comm{"\mmrel" [°scale°] °delimiter°} interprets the °delimiter° as a relational operator.
+
+\comm{"\mmbin" [°scale°] °delimiter°} interprets the °delimiter° as a binary operator.
+
+\comm{"\mmord" [°scale°] °delimiter°} interprets the °delimiter° as an ordinary symbol.
+
+\comm{"\mmop" [°scale°] °delimiter°} interprets the °delimiter° as a large operator.
+
+\comm{"\mmpunct" [°scale°] °delimiter°} interprets the °delimiter° as a punctuation character.
+
+They differ in the way \TeX\ inserts spaces before and after these delimiters, just as if you had written "\mathrel{"°delimiter°"}", "\mathbin{"°delimiter°"}", etc. Like the "\middle" primitive, the "\mmiddle" variant creates a "\mathinner{"°delimiter°"}".
+\begin{code}
+ The scalar product is written as
+ $$ \lleft\langle A_1 \mmiddle| \tilde A_2 \rright\rangle . $$ %%%
+\end{code}
+\begin{exec}
+ The scalar product is written as
+ $$ \lleft\langle A_1 \mmiddle| \tilde A_2 \rright\rangle . $$
+\end{exec}
+\begin{code}
+ There are three parallel lines
+ $$ \lleft. A_1 B_1 \mmrel \Vert A_2 \hat B_2
+ \mmrel \Vert \hat A_2 B_2 \rright. . $$ %%%
+\end{code}
+\begin{exec}
+ There are three parallel lines
+ $$ \lleft. A_1 B_1 \mmrel \Vert A_2 \hat B_2
+ \mmrel \Vert \hat A_2 B_2 \rright. . $$
+\end{exec}
+\begin{code}
+ The centre of mass is defined as
+ $$ \vec R = \lleft( \sum_{k=1}^{n} m_k \, \vec r_k
+ \mmbin / \sum_{k=1}^{n} m_k \rright) . $$ %%%
+\end{code}
+\begin{exec}
+ The centre of mass is defined as
+ $$ \vec R = \lleft( \sum_{k=1}^{n} m_k \, \vec r_k
+ \mmbin / \sum_{k=1}^{n} m_k \rright) . $$
+\end{exec}
+\begin{code}
+ Vectors can be written as tuples like %%%
+ $$ \lleft( \tilde X_1 \mmpunct| \tilde X_2 \mmpunct|
+ \tilde X_3 \mmpunct| \dots \rright) . $$
+\end{code}
+\begin{exec}
+ Vectors can be written as tuples like
+ $$ \lleft( \tilde X_1 \mmpunct| \tilde X_2 \mmpunct|
+ \tilde X_3 \mmpunct| \dots \rright) . $$
+\end{exec}
+\begin{code}
+ This expression makes no sense:
+ $$ \lleft. \int \mmord( \sum \mmord[ \prod \mmord\} \rright. $$
+\end{code}
+\begin{exec}
+ This expression makes no sense:
+ $$ \lleft. \int \mmord( \sum \mmord[ \prod \mmord\} \rright. $$
+\end{exec}
+Unlike the standard "\left"-"\middle"-"\right" primitives, the long variants do not create subformulas or induce grouping at the \TeX\ level. \TeX nically, each delimiter is actually its own group, paired with an invisible delimiter, and with an empty "\hbox" between them. As such, it behaves like a single mathematical symbol of the respective type, or like an atom in \TeX's language. In particular, sub- and superscripts can be attached to individual delimiters, not only to the whole group, and the operator version really behaves like a big operator.
+\begin{code}
+ Applying the triangular square limit, we get
+ $$ \lleft. W(x) + \mmop \uparrow_n^\infty
+ \frac{ 1-x^n }{ 1-x } \rright. = \log( x ). $$ %%%
+\end{code}
+\begin{exec}
+ Applying the triangular square limit, we get
+ $$ \lleft. W(x) + \mmop \uparrow_n^\infty
+ \frac{ 1-x^n }{ 1-x } \rright. = \log( x ). $$
+\end{exec}
+\begin{code}
+ The relations $1^\star$ and $2^\star$ imply the existence of
+ $$ \lleft[ \lleft( A_k \mmrel |^{\lleft(1^\star\rright)}
+ B_k \mmrel |^{\lleft(2^\star\rright)}
+ C_k \rright) \rright] .$$
+\end{code}
+\begin{exec}
+ The relations $1^\star$ and $2^\star$ imply the existence of
+ $$ \lleft[ \lleft( A_k \mmrel |^{\lleft(1^\star\rright)}
+ B_k \mmrel |^{\lleft(2^\star\rright)}
+ C_k \rright) \rright] .$$
+\end{exec}
+Within a formula or subformula, you can have unbalanced groups, or even inner delimiters only. The formula is then interpreted and typeset as if there were invisible delimiters at one or both ends.
+\begin{code}
+ $$ \prod_{p\in\mathbfit{P}} G(p) \mmbin \uparrow
+ \sum_{q\in\mathbfit{Q}} H(q) \mmbin \downarrow
+ \lim_{x\to\infty} F(x) $$
+\end{code}
+\begin{exec}\:
+ $$ \prod_{p\in\mathbfit{P}} G(p) \mmbin \uparrow
+ \sum_{q\in\mathbfit{Q}} H(q) \mmbin \downarrow
+ \lim_{x\to\infty} F(x) $$
+\end{exec}
+\begin{code}
+ A skewed-rational fraction can be written as
+ $$ \frac{ x × \lleft( 1 - x^2 }
+ { x^2 + 1 \rright) × x } . $$ %%%
+\end{code}
+\begin{exec}
+ A skewed-rational fraction can be written as
+ $$ \frac{ x × \lleft( 1 - x^2 }{ x^2 + 1 \rright) × x } . $$
+\end{exec}
+When the formula is processed, each actual delimiter group is temporarily put in a box and the size of that box is determined. Then the dimensions of the empty "\hbox"es inside the fake delimiter groups are set accordingly, so that \TeX\ can apply its algorithm to finally fix the sizes of the delimiters. As this takes place after the whole formula has been transformed into a list of math nodes, no \TeX\ code has to be processed multiple times. It also allows to add some features that cannot be implemented otherwise, unless you are willing to go really deep into macro and token magic.
+
+For example, the limits of large operators like sums or integrals can be excluded from the box that determines the delimiter size. This feature can be turned on and off with two control sequences.
+
+\comm{"\includelimits"} includes the limits of large operators (default).
+
+\comm{"\ignorelimits"} ignores the limits of large operators.
+
+It is possible to use the switch in the middle of a formula or even inside a delimiter group. The switch position at the moment the operator is created determines whether its limits are included or ignored.
+\begin{code}
+ \ignorelimits
+ $$ \vec R = \lleft( \sum_{k=1}^{n} m_k \, \vec r_k
+ \mmbin / \sum_{k=1}^{n} m_k \rright) . $$ %%%
+\end{code}
+\begin{exec}\:
+ \ignorelimits
+ $$ \vec R = \lleft( \sum_{k=1}^{n} m_k \, \vec r_k
+ \mmbin / \sum_{k=1}^{n} m_k \rright) . $$ %%%
+\end{exec}
+\begin{code}
+ $$ \lleft[ \ignorelimits \sum_{A_X^3}^{A_Y^3} F(Z)
+ + \includelimits \sum_a^c f(x) \rright] $$ %%%
+\end{code}
+\begin{exec}\:
+ $$ \lleft[ \ignorelimits \sum_{A_X^3}^{A_Y^3} F(Z)
+ + \includelimits \sum_a^c f(x) \rright] $$ %%%
+\end{exec}
+\begin{code}
+ \includelimits
+ $$ A = 3 × \lleft[ \int\limits_1^5
+ \lleft( 1 + \sqrt{ 1 + x } \rright) \, \mathrm{d}x \rright] $$
+\end{code}
+\begin{exec}\:
+ \includelimits
+ $$ A = 3 × \lleft[ \int\limits_1^5
+ \lleft( 1 + \sqrt{ 1 + x } \rright) \, \mathrm{d}x \rright] $$
+\end{exec}
+\begin{code}
+ \ignorelimits
+ $$ A = 3 × \lleft[ \int\limits_1^5
+ \lleft( 1 + \sqrt{ 1 + x } \rright) \, \mathrm{d}x \rright] $$
+\end{code}
+\begin{exec}\:
+ \ignorelimits
+ $$ A = 3 × \lleft[ \int\limits_1^5
+ \lleft( 1 + \sqrt{ 1 + x } \rright) \, \mathrm{d}x \rright] $$
+\end{exec}
+There are several ways to fine tune the sizes of delimiters. A priori, the minimal size of all delimiters within a group is determined by the total height and depth of everything in this group, except for the delimiters themselves. \TeX\ then checks if a predefined character with the required size exists in the current math font and uses it. Otherwise it creates a stretched symbol with the required size.
+
+Two plain \TeX\ parameters modify the finally chosen sizes of all delimiters. By default, they are used to shrink them slightly, which makes the final formula look less bulky while keeping it readably. This can be seen in the integral example above. An additional third parameter is provided by the \p{longmath} package, which applies to middle delimiters only.
+
+\comm{"\delimiterfactor" °factor°} scales all delimiters by $°factor°/1000$
+(default \the\delimiterfactor).
+
+\comm{"\delimitershortfall" °dimension°} limits the shrinkage of delimiters to the °dimension°
+(default \the\delimitershortfall).
+
+\comm{"\delimiterscale" °factor°} additionally scales middle delimiters by $°factor°/1000$
+(default \the\delimiterscale).
+
+The parameters "\delimiterfactor" (a count register) and "\delimitershortfall" (a dimension register) are explained in the \TeX book. Briefly summarised, every delimiter is finally resized by the given $°factor°/1000$, but the shrinkage is limited to the given °dimension°. The "\delimiterscale" (another count register) is applied to all middle delimiters before the final scaling is applied, and there is no limited shrinkage at this stage.
+\begin{code}
+ \delimiterscale 400
+ $$ \vec R = \lleft( \sum_{k=1}^{n} m_k \, \vec r_k
+ \mmbin / \sum_{k=1}^{n} m_k \rright) . $$ %%%
+\end{code}
+\begin{exec}\:
+ \delimiterscale 400
+ $$ \vec R = \lleft( \sum_{k=1}^{n} m_k \, \vec r_k
+ \mmbin / \sum_{k=1}^{n} m_k \rright) . $$ %%%
+\end{exec}
+Instead of changing "\delimiterscale", the scale factor can also be applied to each delimiter individually, using the optional °scale° argument. In this case, the "\delimiterscale" is ignored and replaced by the given number.
+\begin{code}
+ $$ X = \lleft ( \sum_k^\infty A \mmbin 900 | \sum_k^\infty B
+ \mmbin 700 | \sum_k^\infty C \mmbin 500 | \sum_k^\infty D
+ \mmbin 300 | \sum_k^\infty E \rright ) $$
+\end{code}
+\begin{exec}\:
+ $$ X = \lleft ( \sum_k^\infty A \mmbin 900 | \sum_k^\infty B
+ \mmbin 700 | \sum_k^\infty C \mmbin 500 | \sum_k^\infty D
+ \mmbin 300 | \sum_k^\infty E \rright ) $$
+\end{exec}
+If you want to produce strange and ugly results, you can even scale the opening and closing delimiters independently, or choose factors greater than $1000$.
+\begin{code}
+ $$ \vec R = \lleft 900 ( \sum_{k=1}^{n} m_k \, \vec r_k
+ \mmbin 1300 \divslash \sum_{k=1}^{n} m_k \rright 700 ) $$
+\end{code}
+\begin{exec}\:
+ $$ \vec R = \lleft 900 ( \sum_{k=1}^{n} m_k \, \vec r_k
+ \mmbin 1300 \divslash \sum_{k=1}^{n} m_k \rright 700 ) $$
+\end{exec}
+
+
+\section{Alignment}
+\label{align}
+
+There are two more special delimiter types not listed in section~\ref{delims}. They are treated as middle delimiters regarding the logical grouping, but when they are finally typeset, they are treated as opening and closing delimiters.
+
+\comm{"\mleft" [°scale°] °delimiter°} typesets a left °delimiter°, but does not open a new group.
+
+\comm{"\mright" [°scale°] °delimiter°} typesets a right °delimiter°, but does not close the current group.
+
+To see what they are useful for, consider the following example.
+\begin{code}
+ $$ \lleft( A + \sum B \rright)
+ \lleft( C + \int D \rright) $$
+\end{code}
+\begin{exec}\:
+ $$ \lleft( A + \sum B \rright)
+ \lleft( C + \int D \rright) $$
+\end{exec}
+Can we make the brackets to have the same size? A few "\vphantom"s would probably fix it. But then we need to know in advance which symbols are the responsible ones, or we have to place the content of each bracket as a phantom inside the other one and make \TeX\ process everything twice. Alternatively, we could include everything in a single group and make the two brackets in the middle inner delimiters. But then the correct spacing between them and the adjacent symbols is lost.
+\begin{code}
+ $$ \lleft( A + \sum B \mmiddle )
+ \mmiddle ( C + \int D \rright) $$
+\end{code}
+\begin{exec}\:
+ $$ \lleft( A + \sum B \mmiddle )
+ \mmiddle 1000 ( C + \int D \rright) $$
+\end{exec}
+With "\mleft" and "\mright", we get the same sizes and the correct spacing. Since in this case we only have these two pairs of brackets, we can simple make the whole formula a single group.
+\begin{code}
+ $$ \mleft( A + \sum B \mright)
+ \mleft( C + \int D \mright) $$
+\end{code}
+\begin{exec}\:
+ $$ \mleft( A + \sum B \mright)
+ \mleft( C + \int D \mright) $$
+\end{exec}
+What "\mleft" and "\mright" basically do is not to adapt the opening and closing delimiters to the content between them, but align them with the enclosing group.
+\begin{code*}
+ $$ \lleft( \mleft( 1 + 2 \mright) \mleft( 1 + 2^2 \mright) \mleft( 1 + 2^{2^2} \mright)
+ \mleft( 1 + 2^{2^{2^2}} \mright) + \mleft( 1 + 3 \mright) \mleft( 1 + 3^3 \mright)
+ \mleft( 1 + 3^{3^3} \mright) + \mleft( 1 + 5 \mright) \mleft( 1 + 5^5 \mright) \rright) $$
+\end{code*}
+\begin{exec}\:
+ $$ \lleft( \mleft( 1 + 2 \mright) \mleft( 1 + 2^2 \mright) \mleft( 1 + 2^{2^2} \mright)
+ \mleft( 1 + 2^{2^{2^2}} \mright) + \mleft( 1 + 3 \mright) \mleft( 1 + 3^3 \mright)
+ \mleft( 1 + 3^{3^3} \mright) + \mleft( 1 + 5 \mright) \mleft( 1 + 5^5 \mright) \rright) $$
+\end{exec}
+But what if there is more stuff inside the enclosing group and we only want to align particular brackets with each other? Something like this, and we want both the square and the round brackets to be aligned.
+\begin{code*}
+ $$ F\lleft[ X \rright] \lleft( 1 + 2^2 \rright) \lleft( 1 + 2^{2^2} \rright)
+ + F\lleft[ X^2 \rright] \lleft( 1 + 3^3 \rright) \lleft( 1 + 3^{3^3} \rright)
+ + F\lleft[ X^3 \rright] \lleft( 1 + 5^5 \rright) \lleft( 1 + 5^{5^5} \rright) $$
+\end{code*}
+\begin{exec}\:
+ $$ F\lleft[ X \rright] \lleft( 1 + 2^2 \rright) \lleft( 1 + 2^{2^2} \rright)
+ + F\lleft[ X^2 \rright] \lleft( 1 + 3^3 \rright) \lleft( 1 + 3^{3^3} \rright)
+ + F\lleft[ X^3 \rright] \lleft( 1 + 5^5 \rright) \lleft( 1 + 5^{5^5} \rright) $$
+\end{exec}
+Now we definitely need "\vphantom"s, right? In principle, yes, but the \p{longmath} package provides a way to set them up automatically and hide them inside the delimiters. To use this feature, we have to attach a tag to those delimiters we want to align. The tag is enclosed in braces and placed between the control sequence and the delimiter symbol.
+
+\comm{"\"°type° "{"°tag°"}" [°scale°] °delimiter°} aligns this °delimiter° with all delimiters with the same °tag°.
+
+Here, "\"°type° is any of the above delimiter types, and the °tag° is any string that would also do as, say, an argument to "\label". The basic idea is to make all delimiters with the same °tag° attached to them behave as if they belong to the same group. So, the problem just posed can be solved as follows.
+\begin{code*}
+ $$ F\lleft {Fx} [ X \rright] \lleft {Pw} ( 1 + 2^2 \rright) \lleft {Pw} ( 1 + 2^{2^2} \rright)
+ + F\lleft {Fx} [ X^2 \rright] \lleft {Pw} ( 1 + 3^3 \rright) \lleft {Pw} ( 1 + 3^{3^3} \rright)
+ + F\lleft {Fx} [ X^3 \rright] \lleft {Pw} ( 1 + 5^5 \rright) \lleft {Pw} ( 1 + 5^{5^5} \rright) $$
+\end{code*}
+\begin{exec}\:
+ $$ F\lleft {Fx} [ X \rright] \lleft {Pw} ( 1 + 2^2 \rright) \lleft {Pw} ( 1 + 2^{2^2} \rright)
+ + F\lleft {Fx} [ X^2 \rright] \lleft {Pw} ( 1 + 3^3 \rright) \lleft {Pw} ( 1 + 3^{3^3} \rright)
+ + F\lleft {Fx} [ X^3 \rright] \lleft {Pw} ( 1 + 5^5 \rright) \lleft {Pw} ( 1 + 5^{5^5} \rright) $$
+\end{exec}
+It is enough to tag only the left delimiters, or just one delimiter per group, because those belonging to the same group are aligned anyway. What we actually do is to align groups with each other, not individual delimiters.
+
+The groups being linked need not belong to a common subformula. Here we want to align the brackets and the division slashed in the numerators.
+\begin{code}
+ $$ \frac{ \lleft ( a + 1 \rright) \mmord / x }{a}
+ + \frac{ \lleft ( \sum_k b^k \rright) \mmord / y }{b}
+ + \frac{ \lleft ( c^2 + 1 \rright) \mmord / z }{c} $$ %%%
+\end{code}
+\begin{exec}\:
+ $$ \frac{ \lleft ( a + 1 \rright) \mmord / x }{a}
+ + \frac{ \lleft ( \sum_k b^k \rright) \mmord / y }{b}
+ + \frac{ \lleft ( c^2 + 1 \rright) \mmord / z }{c} $$
+\end{exec}
+This is how it can be done.
+\begin{code}
+ $$ \frac{ \lleft {f5} ( a + 1 \rright) \mmord / x }{a}
+ + \frac{ \lleft {f5} ( \sum_k b^k \rright) \mmord / y }{b}
+ + \frac{ \lleft {f5} ( c^2 + 1 \rright) \mmord / z }{c} $$
+\end{code}
+\begin{exec}\:
+ $$ \frac{ \lleft {f5} ( a + 1 \rright) \mmord / x }{a}
+ + \frac{ \lleft {f5} ( \sum_k b^k \rright) \mmord / y }{b}
+ + \frac{ \lleft {f5} ( c^2 + 1 \rright) \mmord / z }{c} $$
+\end{exec}
+Note that the division slashes are outside the linked groups. But in each numerator, the slash belongs to the enclosing group, which is the subformula formed by the numerator. As this group also contains the brackets, the size of the slash is also adapted to them and thus all slashes get the same size as well. Alternatively, we could have done it like this.
+\begin{code}
+ $$ \frac{ \mleft ( a + 1 \mright) \mmord {f6} / x }{a}
+ + \frac{ \mleft ( \sum_k b^k \mright) \mmord {f6} / y }{b}
+ + \frac{ \mleft ( c^2 + 1 \mright) \mmord {f6} / z }{c} $$
+\end{code}
+\begin{exec}\:
+ $$ \frac{ \mleft ( a + 1 \mright) \mmord {f6} / x }{a}
+ + \frac{ \mleft ( \sum_k b^k \mright) \mmord {f6} / y }{b}
+ + \frac{ \mleft ( c^2 + 1 \mright) \mmord {f6} / z }{c} $$
+\end{exec}
+But not like this, which aligns the slashes, but not the brackets.
+\begin{code}
+ $$ \frac{ \lleft ( a + 1 \rright) \mmord {f7} / x }{a}
+ + \frac{ \lleft ( \sum_k b^k \rright) \mmord {f7} / y }{b}
+ + \frac{ \lleft ( c^2 + 1 \rright) \mmord {f7} / z }{c} $$
+\end{code}
+\begin{exec}\:
+ $$ \frac{ \lleft ( a + 1 \rright) \mmord {f7} / x }{a}
+ + \frac{ \lleft ( \sum_k b^k \rright) \mmord {f7} / y }{b}
+ + \frac{ \lleft ( c^2 + 1 \rright) \mmord {f7} / z }{c} $$
+\end{exec}
+For the alignment to work, the linked delimiters need not be visible. An invisible inner delimiter basically behaves like a "\vphantom", whose content is the whole group to which this delimiter belongs. More precisely, it is actually the empty "\hbox" that is present in our special delimiters which replaces the "\vphantom". We can use this to align the square roots in the following example.
+\begin{code}
+ $$ \sqrt{ \mleft {sq8} . a + 1 }
+ + \sqrt{ \mleft {sq8} . \sum\nolimits_k b_k } %%%
+ + \sqrt{ \mleft {sq8} . c^2 + 1 } $$
+\end{code}
+\begin{exec}\:
+ $$ \sqrt{ \mleft {sq8} . a + 1 }
+ + \sqrt{ \mleft {sq8} . \sum\nolimits_k b_k }
+ + \sqrt{ \mleft {sq8} . c^2 + 1 } $$
+\end{exec}
+Why did we use "\mleft" here, and not one of the middle delimiters? The first reason is that putting an opening atom at the beginning of a subformula will get the horizontal spacing correct, independent of what kind of symbol follows. The second reason is that a middle delimiter would be scaled by "\delimiterscale", so that it wouldn't have the required size to align the heights of the square roots. The "\delimiterfactor" is irrelevant here, because it doesn't affect an invisible delimiter.
+
+We can even go a step further and link delimiters in different equations and, in fact, all across the document.
+\begin{code}
+ Equations that start with
+ $$ \lleft( \int\limits_1^\infty f(x) \, \mathrm{d}x \rright)
+ \lleft {longeq} ( \sum_k a_k + 1000 + 999 + \cdots $$
+ often go on for pages and end with
+ $$ \cdots + 1 \rright {longeq} ) = 0 . $$
+\end{code}
+\begin{exec}
+ Equations that start with
+ $$ \lleft( \int\limits_1^\infty f(x) \, \mathrm{d}x \rright)
+ \lleft {longeq} ( \sum_k a_k + 1000 + 999 + \cdots $$
+ often go on for pages and end with
+ $$ \cdots + 1 \rright {longeq} ) = 0 . $$
+\end{exec}
+To make this possible, the \LaTeX\ method of storing information in the "aux" file is used. The dimensions of all tagged delimiter groups are saved and reloaded when the document is typeset the next time. It therefore requires a second run to get the sizes of linked delimiters correct. If the size of any delimiter changes while the typesetting is in progress, a warning message is issued at the end, similar to the message that you need to rerun the document to get all the "\ref"erences to "\label"s right.
+
+You will also get a warning message if you set up the tags so that some delimiter group is linked to its own parent or subgroup. In such a case, there is no consistent way to determine the required sizes. With each run, they will change randomly or even start to increase and diverge. It's okay, however, to link delimiters in different math styles. You can link an exponent in script style to its own basis in display style, although this might not make much sense.
+
+For all this to work, the tags must be document wide unique. But unlike equation labels or bibliography entries, they typically only link delimiters within a single equation or an array of equations. It is therefore preferable to have short tags like the ones used above, or even simple letters or numbers. To make this possible, you can locally define a °prefix°, which is added to each tag used within its scope.
+
+\comm{"\delimiterprefix{"°prefix°"}"} prepends the °prefix° to the tags of all tagged delimiters.
+
+It is then sufficient to use a document wide unique prefix for each section, equation, or whatever scope suits best. The setting is local to the \TeX\ group, but if the same prefix is used at different places, the tagged delimiters within those scopes are linked to each other. For example, if the prefix is "introduction-" and the tag "a2" is attached to a delimiter, then the actual tag used internally is "introduction-a2", and this is a global name.
+
+You can even automate this, by setting a dynamical prefix like this in the preamble.
+\begin{code*}
+ \delimiterprefix{\theequation.}
+\end{code*}
+Provided that you use unique equations numbers, and link delimiters within single equations only, your tags will then always be local to the current equation, something like "5.16.a" if the tag "a" is used in equation~(5.16).
+
+The perhaps most useful application of tagged delimiters is to typeset formulas that extend over several cells or lines in an array. Here is a little challenge. In the following array, we want the multiplication and addition operators to be aligned vertically, and of course the brackets to be adapted to the content they enclose. We use the \p{array} package to define a few special column types to get the math style and the spacing correct.
+\begin{code*}
+ \newcolumntype{d}{ >{\displaystyle} c }
+ \newcolumntype{s}{ @{} >{{}} c <{{}} @{} }
+\end{code*}
+ \newcolumntype{d}{ >{\displaystyle} c }
+ \newcolumntype{s}{ @{} >{{}} c <{{}} @{} }
+\begin{code}
+ $$ \begin{array}{dsdsdsd}
+ \lleft [ Z & - & \sum_k A & × &
+ \lleft ( B & + & C^X \rright ) \rright ] \\[3ex] %%%
+ \lleft [ P & × & \lleft ( U & + &
+ \int_R V \rright ) & - & W^Y \rright ]
+ \end{array} $$
+\end{code}
+\begin{exec}
+ $$ \begin{array}{dsdsdsd}
+ \lleft[ Z & - & \sum_k A & × &
+ \lleft( B & + & C^X \rright) \rright] \\[3ex]
+ \lleft[ P & × & \lleft( U & + &
+ \int_R V \rright ) & - & W^Y \rright]
+ \end{array} $$
+\end{exec}
+In an "array", each cell is typeset as a separate formula. Therefore, the delimiters in each cell are adapted only to the content of this particular cell. Apparently, it would be quite a complicated task to fix this with "\vphantom"s. Let's try to simply link the delimiters that belong together.
+\begin{code}
+ $$ \begin{array}{dsdsdsd}
+ \lleft {a1} [ Z & - & \sum_k A & × &
+ \lleft {b1} ( B & + & C^X \rright {b1} ) \rright {a1} ] \\[3ex]
+ \lleft {a2} [ P & × & \lleft {b2} ( U & + &
+ \int_R V \rright {b2} ) & - & W^Y \rright {a2} ]
+ \end{array} $$
+\end{code}
+\begin{exec}
+ $$ \begin{array}{dsdsdsd}
+ \lleft {a1} [ Z & - & \sum_k A & × &
+ \lleft {b1} ( B & + & C^X \rright {b1} ) \rright {a1} ] \\[3ex]
+ \lleft {a2} [ P & × & \lleft {b2} ( U & + &
+ \int_R V \rright {b2} ) & - & W^Y \rright {a2} ]
+ \end{array} $$
+\end{exec}
+A first step, but not the expected result. It seems that we have to look at it more closely and sort out which parts of which cells actually belong to the same groups. But there is in fact a simple solution that doesn't require any deeper thinking or sorting out. We just need to connect the end of each cell to the beginning of the next one. We can do this by inserting invisible delimiters.
+\begin{code}
+ $$ \begin{array}{dsdsdsd}
+ \lleft[ Z \mright{c1}. & - & \mleft{c1}. \sum_k A \mright{c2}.
+ & × & \mleft{c2}. \lleft( B \mright{c3}. & + &
+ \mleft{c3}. C^X \rright ) \rright ] \\[3ex]
+ \lleft[ P \mright{d1}. & × & \mleft{d1}. \lleft ( U \mright{d2}.
+ & + & \mleft{d2}. \int_R V \rright ) \mright{d3}. & - &
+ \mleft{d3}. W^Y \rright ] \end{array} $$
+\end{code}
+\begin{exec}
+ $$ \begin{array}{dsdsdsd}
+ \lleft[ Z \mright{c1}. & - & \mleft{c1}. \sum_k A \mright{c2}.
+ & × & \mleft{c2}. \lleft( B \mright{c3}. & + &
+ \mleft{c3}. C^X \rright ) \rright ] \\[3ex]
+ \lleft[ P \mright{d1}. & × & \mleft{d1}. \lleft ( U \mright{d2}.
+ & + & \mleft{d2}. \int_R V \rright ) \mright{d3}. & - &
+ \mleft{d3}. W^Y \rright ] \end{array} $$
+\end{exec}
+This is the desired output, but the code starts to look complicated. But it is a simple rule, so there should be a way to automate it. Can we include the invisible linking delimiters into the cell template? The problem is that we need a unique tag for each transition from one cell to the next. For this purpose, two macros are provided.
+
+\comm{"\pushdelimiter"} inserts an invisible closing delimiter with an automatically generated unique tag.
+
+\comm{"\pulldelimiter"} inserts an invisible opening delimiter with the previously generated tag.
+
+The generated tags are stored on a stack, so that multiple push and pull operations can be nested. They are globally unique and survive the current \TeX\ group. If we include the macros into the cell template, they connect each cell to the next one. We can also define templates that start a new sequence or end the current one. As Lua\TeX\ allows this, let's use Greek letters for the beginning, the middle, and the end of a row.
+\begin{code*}
+ \newcolumntype{α}{ >{ \displaystyle } c <{ \pushdelimiter } }
+ \newcolumntype{μ}{ >{ \displaystyle \pulldelimiter } c <{ \pushdelimiter } }
+ \newcolumntype{ω}{ >{ \displaystyle \pulldelimiter } c }
+\end{code*}
+ \newcolumntype{α}{ >{ \displaystyle } c <{ \pushdelimiter } }
+ \newcolumntype{μ}{ >{ \displaystyle \pulldelimiter } c <{ \pushdelimiter } }
+ \newcolumntype{ω}{ >{ \displaystyle \pulldelimiter } c }
+Now the code is as simple as it was in the beginning. We can just ignore the cell boundaries and write the formula as if it was a single line in "$$"s, or whatever single line math environment.
+\begin{code}
+ $$ \begin{array}{αsμsμsω}
+ \lleft [ Z & - & \sum_k A & × &
+ \lleft ( B & + & C^X \rright ) \rright ] \\[3ex] %%%
+ \lleft [ P & × & \lleft ( U & + &
+ \int_R V \rright ) & - & W^Y \rright ]
+ \end{array} $$
+\end{code}
+\begin{exec}\.[-4ex]
+ $$ \begin{array}{αsμsμsω}
+ \lleft [ Z & - & \sum_k A & × &
+ \lleft ( B & + & C^X \rright ) \rright ] \\[3ex] %%%
+ \lleft [ P & × & \lleft ( U & + &
+ \int_R V \rright ) & - & W^Y \rright ]
+ \end{array} $$
+\end{exec}
+If you want the formula to continue on the next line with pending delimiters, you can include the push and pull macros into the line separator as well.
+\begin{code}
+ \newcommand\continue[1][0pt]{ \pushdelimiter \\[#1] \pulldelimiter }
+ $$ \begin{array}{αsμsμsω}
+ \lleft \{ Z & - & \sum_k \lleft ( A & × &
+ B^3 \rright ) & + & \continue[3ex] %%%
+ & + & \lleft ( U & + &
+ \int_R V \rright ) & - & W^Y \rright \}
+ \end{array} $$
+\end{code}
+\begin{exec}
+ \newcommand\continue[1][0pt]{ \pushdelimiter \\[#1] \pulldelimiter }
+ $$ \begin{array}{αsμsμsω}
+ \lleft \{ Z & - & \sum_k \lleft ( A & × &
+ B^3 \rright ) & + & \continue[3ex] %%%
+ & + & \lleft ( U & + &
+ \int_R V \rright ) & - & W^Y \rright \}
+ \end{array} $$
+\end{exec}
+A construction like this can also be used with \p{amsmath} environments like "multiline" or "split" , which require explicit line breaks to be inserted into long formulas. The "longmath" environment described in section~\ref{multiline} provides an alternative method to typeset long formulas without the need to specify the breakpoints manually.
+
+\section{Automatic Nesting}
+
+It is possible to select the kinds of brackets used as outer delimiters automatically depending on the nesting level. This might not add much value for formulas that are explicitly written into the document. But it could be useful for semi-automatically produced formulas, where the author doesn't have direct control over individual symbols.
+
+The basic idea is to define a collection of delimiter pairs, and let \TeX\ pick them according to the current nesting level.
+
+\comm{"\autodelimiters{"°sample°"}"} defines a collection of nested delimiters pairs.
+
+The °sample° must be an otherwise empty, nested structure of delimiters enclosing an asterisk "*" as a single object.
+ \autodelimiters{ \lfloor [ \{ ( * ) \} ] \rceil }
+\begin{code*}
+ \autodelimiters{ \lfloor [ \{ ( * ) \} ] \rceil }
+\end{code*}
+Once this is set up, you can pick one of them by using "*" as a variable delimiter.
+\begin{code}
+ The corrected Sellmann-Yokuzi contribution to the
+ Johansson Entropy is given by
+ $$ S^\top = k · \lleft* A + \lleft* B_C +
+ \lleft* D^E \rright* ^ 2 \rright*
+ ^ { \lleft* 3+κ \rright* ^2 } + F \rright* $$
+\end{code}
+\begin{exec}
+ The corrected Sellmann-Yokuzi contribution to the
+ Johansson Entropy is given by
+ $$ S^\top = k · \lleft* A + \lleft* B_C +
+ \lleft* D^E \rright* ^ 2
+ \rright* ^ { \lleft* 3+κ \rright* ^2 } + F \rright* $$
+\end{exec}
+If the nesting level exceeds the number of sample pairs provided, the outermost pair is repeated.
+\begin{code+}
+ $$ \lleft* A + \lleft* B + \lleft* C + \lleft*
+ D + \frac{ \lleft* X + Y \rright* × Z }{ W }
+ \rright* · E \rright* · F \rright* · G \rright* $$
+\end{code+}
+\begin{exec}\:
+ $$ \lleft* A + \lleft* B + \lleft* C + \lleft*
+ D + \frac{ \lleft* X + Y \rright* × Z }{ W }
+ \rright* · E \rright* · F \rright* · G \rright* $$
+\end{exec}
+If variable delimiters are mixed with explicit delimiters, there is an interaction between them. If you use one of the pairs from the collection, it is treated as if it was inserted automatically. Any enclosing bracket will be the next one from the collection. Let's define a larger collection to demonstrate this.
+ \autodelimiters{ < \lceil \lfloor [ \{ ( * ) \} ] \rfloor \rceil > }
+\begin{code*}
+ \autodelimiters{ < \lceil \lfloor [ \{ ( * ) \} ] \rfloor \rceil > }
+\end{code*}
+This is what we get with automatic brackets only. The numbers indicate the actually used level. A negative value for "\delimitershortfall" makes the bracket size increase linearly with the nesting level.
+\begin{code}
+ \delimitershortfall -2pt
+ $$ \lleft* 5 + \lleft* 4 + \lleft* 3
+ + \lleft* 2 + \lleft* 1 + A \rright* + B \rright*
+ + C \rright* + D \rright* + E \rright* $$
+\end{code}
+\begin{exec}
+ \delimitershortfall -2pt
+ $$ \lleft* 5 + \lleft* 4 + \lleft* 3
+ + \lleft* 2 + \lleft* 1 + A \rright* + B \rright*
+ + C \rright* + D \rright* + E \rright* $$
+\end{exec}
+If we replace the second level by explicit square brackets, these are identified as the third level, and the counting continues from there.
+\begin{code}
+ $$ \lleft* 6 + \lleft* 5 + \lleft* 4
+ + \lleft[ 3 + \lleft* 1 + A \rright* + B \rright]
+ + C \rright* + D \rright* + E \rright* $$
+\end{code}
+\begin{exec}\:
+ \delimitershortfall -2pt
+ $$ \lleft* 6 + \lleft* 5 + \lleft* 4
+ + \lleft[ 3 + \lleft* 1 + A \rright* + B \rright]
+ + C \rright* + D \rright* + E \rright* $$
+\end{exec}
+And here the counting restarts further out.
+\begin{code}
+ $$ \lleft* 2 + \lleft( 1 + \lleft* 3
+ + \lleft* 2 + \lleft* 1 + A \rright* + B \rright*
+ + C \rright* + D \rright) + E \rright* $$
+\end{code}
+\begin{exec}\:
+ \delimitershortfall -2pt
+ $$ \lleft* 2 + \lleft( 1 + \lleft* 3
+ + \lleft* 2 + \lleft* 1 + A \rright* + B \rright*
+ + C \rright* + D \rright) + E \rright* $$
+\end{exec}
+A pair that is not in the collection is ignored and the counting continues at the next level.
+\begin{code}
+ $$ \lleft* 4 + \lleft* 3 + \lleft\uparrow 0
+ + \lleft* 2 + \lleft* 1 + A \rright* + B \rright*
+ + C \rright\downarrow + D \rright* + E \rright* $$
+\end{code}
+\begin{exec}\:
+ \delimitershortfall -2pt
+ $$ \lleft* 4 + \lleft* 3 + \lleft\uparrow 0
+ + \lleft* 2 + \lleft* 1 + A \rright* + B \rright*
+ + C \rright\downarrow + D \rright* + E \rright* $$
+\end{exec}
+If variable delimiters are tagged and aligned, this also applies to the chosen delimiter from the collection. Here is a typical example. Without tagging, we get this.
+\begin{code}
+ $$ \lleft * \frac{ n+1 }{ n^2 } \rright * ^2
+ + \lleft * \frac{ n }{ \lleft * n - 1 \rright *^2 } %%%
+ \rright * ^2 $$
+\end{code}
+\begin{exec}\:
+ $$ \lleft * \frac{ n+1 }{ n^2 } \rright * ^2
+ + \lleft * \frac{ n }{ \lleft * n - 1 \rright *^2}
+ \rright * ^2 $$
+\end{exec}
+If the outer brackets are aligned, both become second level brackets.
+\begin{code}
+ $$ \lleft {qx} * \frac{ n+1 }{ n^2 } \rright * ^2
+ + \lleft {qx} * \frac{ n }{ \lleft * n - 1 \rright *^2}
+ \rright * ^2 $$
+\end{code}
+\begin{exec}\:
+ $$ \lleft {qx} * \frac{ n+1 }{ n^2 } \rright * ^2
+ + \lleft {qx} * \frac{ n }{ \lleft * n - 1 \rright *^2}
+ \rright * ^2 $$
+\end{exec}
+As a last challenge, let's consider the array from the previous section with a slight modification. If we replace all brackets with variable delimiters, but do not link the cells, we get this.
+\begin{code}
+ $$ \begin{array}{dsdsdsd}
+ \lleft * Z & - & \sum_k A & × &
+ \lleft * B & + & C^X \rright * \rright * \\[3ex] %%%
+ \lleft * P & × & \lleft * \lleft * U^2 \rright * & + &
+ \int_R V \rright * & - & W^Y \rright *
+ \end{array} $$
+\end{code}
+\begin{exec}\.[-4ex]
+ $$ \begin{array}{dsdsdsd}
+ \lleft * Z & - & \sum_k A & × &
+ \lleft * B & + & C^X \rright * \rright * \\[3ex] %%%
+ \lleft * P & × & \lleft * \lleft * U^2 \rright * & + &
+ \int_R V \rright * & - & W^Y \rright *
+ \end{array} $$
+\end{exec}
+As expected, only the brackets within each cell are set up properly, but with no relation to those in the other cells. If we use the version with linked cells, we get the intended result.
+\begin{code}
+ $$ \begin{array}{αsμsμsω}
+ \lleft * Z & - & \sum_k A & × &
+ \lleft * B & + & C^X \rright * \rright * \\[3ex] %%%
+ \lleft * P & × & \lleft * \lleft * U^2 \rright * & + &
+ \int_R V \rright * & - & W^Y \rright *
+ \end{array} $$
+\end{code}
+\begin{exec}\.[-4ex]
+ \delimiterprefix{A}
+ $$ \begin{array}{αsμsμsω}
+ \lleft * Z & - & \sum_k A & × &
+ \lleft * B & + & C^X \rright * \rright * \\[3ex] %%%
+ \lleft * P & × & \lleft * \lleft * U^2 \rright * & + &
+ \int_R V \rright * & - & W^Y \rright *
+ \end{array} $$
+\end{exec}
+
+
+\section{Multi Line Formulas}
+\label{multiline}
+Unlike the \TeX\ primitives, the \p{longmath} macros do not create a subformula that cannot be broken into lines. In inline math mode, long formulas with delimiter groups can extend over several lines just like ordinary text.
+\begin{code*}
+ \newcommand\polyA{ x^{10} + x^9 - x^8 + 2 x^6 + 9 x^5 + 2 x^4 - 4 x^3 - 2 x^2 + 6 x + 4 }
+ \newcommand\polyB{ x^4 - x^3 + x + 2 } \newcommand\polyC{ x^6 + 2 x^5 + x^4 - 2 x^2 + 2 x + 2 }
+\end{code*}
+ \newcommand\polyA{ x^{10} + x^9 - x^8 + 2 x^6 + 9 x^5 + 2 x^4 - 4 x^3 - 2 x^2 + 6 x + 4 }
+ \newcommand\polyB{ x^4 - x^3 + x + 2 } \newcommand\polyC{ x^6 + 2 x^5 + x^4 - 2 x^2 + 2 x + 2 }
+\begin{code+}
+ Factorising the polynomial
+ $ P\lleft( \polyA \rright) $
+ gives $ P \lleft( \polyB \rright)
+ · P \lleft( \polyC \rright) $. %%%
+\end{code+}
+\begin{exec*}\openup 1pt
+ Factorising the polynomial
+ $ P\lleft( \polyA \rright) $
+ gives $ P \lleft( \polyB \rright)
+ · P \lleft( \polyC \rright) $.
+\end{exec*}
+There is no interference between nested delimiter groups and logical \TeX\ groups. Delimiters can be placed deep inside "\begingroup"-"\endgroup" constructs, and they do not create such groups themselves. The following example uses the \p{multido} package. It wouldn't work with "\left", "\right" and "\middle".
+\begin{code}
+ Applying the Hofstadt Zebelajev theorem, we conclude that
+ $ \lleft( \hat A_0
+ \multido{\i=1+1}{10}{ \mmbin \uparrow \hat A_{\i} }
+ \rright) \in \lleft[\mathcal{H}^\infty\rright]^{11}$.
+\end{code}
+\begin{exec*}\openup 2pt
+ Applying the Hofstadt Zebelajev theorem, we conclude that
+ $ \lleft( \hat A_0
+ \multido{\i=1+1}{10}{ \mmbin \uparrow \hat A_{\i} }
+ \rright) \in \lleft[\mathcal{H}^\infty\rright]^{11}$.
+\end{exec*}
+For multi line formulas in display mode, \p{longmath} provides a special environment.
+
+\comm{"\begin{longmath}["°align°"]{"°width°"}"} begins a multi line expression in display mode.
+
+\comm{"\end{longmath}"} ends a multi line expression in display mode.
+
+It works like the "minipage" environment. You have to specify the °width° of the box the formula will by placed into, and how to °align° it vertically with the rest of the displayed equation. The optional alignment argument is either "c" for centre, which is the default, or "t" for top or "b" for bottom.
+\begin{code*}
+ \newcommand\harm[2]{ \frac{1}{#1} \mmultido{\n=#1+1}{#2}{ + \frac{1}{\n} } }
+\end{code*}
+ \newcommand\harm[2]{ \frac{1}{#1} \mmultido{\n=#1+1}{#2}{ + \frac{1}{\n} } }
+\begin{code+}
+ The harmonic series starts with
+ $$ Q = \begin{longmath}[t]{70mm}
+ 1 + \harm{2}{21} + \cdots %%%
+ \end{longmath} $$
+ and diverges very slowly.
+\end{code+}
+\begin{exec}
+ The harmonic series starts with
+ $$ Q = \begin{longmath}[t]{70mm}
+ 1 + \harm{2}{21} + \cdots
+ \end{longmath} $$
+ and diverges very slowly.
+\end{exec}
+No markers are needed to force line breaks at particular points. \TeX's line breaking algorithm is used to find the ``best'' breakpoints. If you're familiar with it, you can insert penalties into the formula to optimize it.
+
+The °width° can be specified either as an absolute dimension or as fraction of the available width of the display.
+\begin{code+}
+ One can easily prove that
+ $$ \begin{longmath}[b]{0.7}
+ 1 + \harm{2}{21} + \cdots
+ \end{longmath} \to \infty, $$
+ which comes as a surprise for some students.
+\end{code+}
+\begin{exec}
+ One can easily prove that
+ $$ \begin{longmath}[b]{0.7}
+ 1 + \harm{2}{21} + \cdots
+ \end{longmath} \to \infty, $$
+ which comes as a surprise for some students.
+\end{exec}
+As formulas typically have fewer possible breakpoints than ordinary text, no attempt is made to justify the lines. Instead, some flexible glue is inserted at both sides of each line to avoid extreme stretching or shrinking. Only the beginning of the first line and the end of the last line are forced to align with the boundary of the box specified by the °width° parameter. If the whole formula fits into a single line, a box with only the required width is created. This ensures that, regardless of the number of lines, a top aligned "longmath" box always aligns neatly with an expression to the left, and the formula in a bottom aligned "longmath" box can be continued to the right.
+
+Since the "longmath" environment creates a vertical box, you can have several of them in a single display.
+\begin{code+}
+ $$ \lleft[ \begin{longmath}[c]{0.33}
+ \polyA
+ \end{longmath} \rright] =
+ \lleft[ \begin{longmath}[c]{0.33}
+ \lleft( \polyB \rright) \cdot %%%
+ \lleft( \polyC \rright)
+ \end{longmath} \rright] $$
+\end{code+}
+\begin{exec}
+ $$ \lleft[ \begin{longmath}[c]{0.33}
+ \polyA
+ \end{longmath} \rright] =
+ \lleft[ \begin{longmath}[c]{0.33}
+ \lleft( \polyB \rright) \cdot
+ \lleft( \polyC \rright)
+ \end{longmath} \rright] $$
+\end{exec}
+To make this look a bit nicer, you can add some extra space to the width of the box and arrange the lines from the top left to the bottom right. If the °width° parameter is given as °linewidth°"+"°extrawidth°, the lines are stacked so that the total width of the box is the sum of °linewidth° and °extrawidth°. Both values can be given as absolute dimensions or as fractions of the total display width.
+\begin{code+}
+ $$ \lleft[ \begin{longmath}[c]{0.33+0.07}
+ \polyA
+ \end{longmath} \rright] =
+ \lleft[ \begin{longmath}[c]{0.33+0.0.7}
+ \lleft( \polyB \rright) \cdot %%%
+ \lleft( \polyC \rright)
+ \end{longmath} \rright] $$
+\end{code+}
+\begin{exec}
+ $$ \lleft[ \begin{longmath}[c]{0.33+0.07}
+ \polyA
+ \end{longmath} \rright] =
+ \lleft[ \begin{longmath}[c]{0.33+0.07}
+ \lleft( \polyB \rright) \cdot
+ \lleft( \polyC \rright)
+ \end{longmath} \rright] $$
+\end{exec}
+A "longmath" box can replace any kind of subformula. Just specify the maximal width the subformula is allowed to have. If it fits into this width, it will be typeset normally. Otherwise, it is split into several lines.
+\begin{code*}
+ $$ \frac{ \begin{longmath}{40mm+10mm} \polyA \end{longmath} }
+ { \begin{longmath}{40mm+10mm} \polyB \end{longmath} }
+ = \begin{longmath}{40mm+10mm} \polyC \end{longmath} $$
+\end{code*}
+\begin{exec}
+ $$ \frac{ \begin{longmath}{40mm+10mm} \polyA \end{longmath} }
+ { \begin{longmath}{40mm+10mm} \polyB \end{longmath} }
+ = \begin{longmath}{40mm+10mm} \polyC \end{longmath} $$
+\end{exec}
+\begin{code*}
+ \newcommand\polyX{ x^5 - x^4 + x^3 - x^2 + x - 1 }
+ \newcommand\polyY{ x^{10} - 2 x^9 + 3 x^8 - 4 x^7 + 5 x^6 - 6 x^5 + 5 x^4 - 4 x^3 + 3 x^2 - 2 x + 1 }
+ $$ \sqrt{ \begin{longmath}{40mm+10mm} \polyY \end{longmath} }
+ = \begin{longmath}{40mm+10mm} \polyX \end{longmath} $$
+\end{code*}
+\begin{exec}
+ \newcommand\polyX{ x^5 - x^4 + x^3 - x^2 + x - 1 }
+ \newcommand\polyY{ x^{10} - 2 x^9 + 3 x^8 - 4 x^7 + 5 x^6 - 6 x^5 + 5 x^4 - 4 x^3 + 3 x^2 - 2 x + 1 }
+ $$ \sqrt{ \begin{longmath}{50mm+10mm} \polyY \end{longmath} }
+ = \begin{longmath}{50mm+10mm} \polyX \end{longmath} $$
+\end{exec}
+
+There is also a smarter "longmath*" environment, which sets the width of the box ``intelligently'', so that the formula is split into equally long segments and the available space is used efficiently. The idea is to specify a maximal width, then let \TeX\ find the number of lines needed, and adapt the width to the total length divided by the number of lines. This will usually result in a box that is slightly shorter than the given width.
+
+Whether this really succeeds depends on a sufficient number of possible breakpoints and stretchable and shrinkable glue in the formula, so that \TeX's algorithm can actually split it into equally long parts. Take it with a grain of salt and don't mind if it doesn't work properly in difficult cases. As an example, here is what we get without this feature.
+\begin{code*}
+ To prove it, observe that each bracket is larger than $\frac 12$:
+ $$ \lleft\{ \begin{longmath}[c]{0.8+0.1}
+ \lleft( 1 + \frac{1}{2} \rright) + \lleft( \harm{3}{1} \rright) + \lleft( \harm{5}{3} \rright) +
+ \lleft( \harm{9}{7} \rright) + \lleft( \harm{17}{15} \rright) + \lleft( \harm{33}{3}
+ + \cdots \rright. \end{longmath} \rright\} $$
+\end{code*}
+\begin{exec}
+ To prove it, observe that each bracket is larger than $\frac 12$:
+ $$ \lleft\{ \begin{longmath}[c]{0.8+0.1}
+ \lleft( 1 + \frac{1}{2} \rright) + \lleft( \harm{3}{1} \rright) + \lleft( \harm{5}{3} \rright) +
+ \lleft( \harm{9}{7} \rright) + \lleft( \harm{17}{15} \rright) + \lleft( \harm{33}{3}
+ + \cdots \rright. \end{longmath} \rright\} $$
+\end{exec}
+If we replace "longmath" by "longmath*", but stick to the same width specification, we get this.
+\begin{exec}
+ There is even a slightly ``shorter'' proof:
+ $$ \lleft\{ \begin{longmath*}[c]{0.8+0.1}
+ \lleft( 1 + \frac{1}{2} \rright) + \lleft( \harm{3}{1} \rright) + \lleft( \harm{5}{3} \rright) +
+ \lleft( \harm{9}{7} \rright) + \lleft( \harm{17}{15} \rright) + \lleft( \harm{33}{3}
+ + \cdots \rright. \end{longmath*} \rright\} $$
+\end{exec}
+The spacing between the lines is controlled by a dimension register that can be adjusted.
+
+\comm{"\longmathlinesep"} dimension to be added to the line skip in the "longmath" environment (default \the\longmathlinesep).
+
+This dimension is added to the current "\baselineskip" and "\lineskip" when the content of the box is split into lines and placed in a vertical box, basically using \TeX's "\openup" macro. Some additional effort is made to adjust the line spacing if the "longmath" environment is placed in a sub- or superscript as in this final example.
+\begin{code*}
+ $$ \Omega = \lleft| \begin{longmath*}[c]{0.6+0.1}
+ \mathscr{Q} + \lleft[ \lleft( T · H · I^{\lleft(a+b\rright)^4} · S \rright)
+ + I · S + A + \lleft\{ V · E^{\lleft(a^2+2ab+b^2\rright)^2} · R · Y + L · O · N · G
+ + \lleft[ \int\limits_I^N T · \lleft( E^{G^R} \rright) · A_L \rright]
+ - \frac{ D^I · V^I }{ D · E · D } \mmbin / \lleft\{ B · Y
+ + A × \sum\limits_{-\infty}^{\infty} O^F \lleft< S^c_a L^a_r \mmiddle | \prod\nolimits^s \rright>
+ \rright\} \rright\} \rright] + C^\infty \end{longmath*} \rright|
+ ^ { \lleft( \begin{longmath*}[c]{25mm+5mm} a+b+c+d+e+f+g+h+i+j+k+l+m \end{longmath*} \rright) } $$
+\end{code*}
+\begin{exec}
+ \ignorelimits
+ $$ \Omega = \lleft| \begin{longmath*}[c]{0.6+0.1}
+ \mathscr{Q} + \lleft[ \lleft( T · H · I^{\lleft(a+b\rright)^4} · S \rright)
+ + I · S + A + \lleft\{ V · E^{\lleft(a^2+2ab+b^2\rright)^2} · R · Y + L · O · N · G
+ + \lleft[ \int\limits_I^N T · \lleft( E^{G^R} \rright) · A_L \rright]
+ - \frac{ D^I · V^I }{ D · E · D } \mmbin / \lleft\{ B · Y
+ + A × \sum\limits_{-\infty}^{\infty} O^F \lleft< S^c_a L^a_r \mmiddle | \prod\nolimits^s \rright>
+ \rright\} \rright\} \rright] + C^\infty \end{longmath*} \rright|
+ ^ { \lleft( \begin{longmath*}[c]{25mm+5mm} a+b+c+d+e+f+g+h+i+j+k+l+m \end{longmath*} \rright) } $$
+\end{exec}
+
+
+\end{document}
diff --git a/macros/luatex/latex/longmath/longmath.lua b/macros/luatex/latex/longmath/longmath.lua
new file mode 100644
index 0000000000..56c64c8cdb
--- /dev/null
+++ b/macros/luatex/latex/longmath/longmath.lua
@@ -0,0 +1,428 @@
+--
+-- longmath.lua is part of longmath version 0.1.
+--
+-- (c) 2024 Hans-Jürgen Matschull
+--
+-- 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 Hans-Jürgen Matschull
+--
+-- see README for a list of files belonging to longmath.
+--
+
+-- Attribute numbers.
+local attr_info = luatexbase.registernumber( 'longmath@info' )
+local attr_limits = luatexbase.registernumber( 'longmath@limits' )
+
+-- Character for tagging parent groups.
+local parent = '+'
+
+
+-- Check for specific node types.
+local ntypes = node.types()
+local function is_noad( nd ) return nd and ntypes[ nd.id ] == 'noad' and nd.subtype end
+local function is_fence( nd ) return nd and ntypes[ nd.id ] == 'fence' and nd.subtype end
+local function is_hlist( nd ) return nd and ntypes[ nd.id ] == 'hlist' and nd.subtype end
+local function is_style( nd ) return nd and ntypes[ nd.id ] == 'style' and nd.subtype end
+local function is_whatsit( nd ) return nd and ntypes[ nd.id ] == 'whatsit' and nd.subtype end
+
+-- Read a command whatsit.
+local function get_comm( nd )
+ if is_whatsit( nd ) == 3 and node.has_attribute( nd, attr_info ) then
+ local comm, data = nd.data:match( '^([^:]+):?(.*)$' )
+ if data == '' then data = nil end
+ return comm, data, node.get_attribute( nd, attr_info )
+ end
+end
+
+-- Iterator over all fields of a node to be copied.
+-- Returns the field name and a boolean indicating if a deep copy is needed.
+-- If the node already has fixed dimensions, its head field is ignored.
+-- If the limits attribute is set on a large operator, scripts are ignored in display style.
+local function fields( nd, disp )
+ local list, oper = {}, is_noad( nd )
+ for _, fld in ipairs( node.fields( nd.id, nd.subtype ) ) do
+ list[fld] = not not node.is_node( nd[fld] )
+ end
+ list.id, list.subtype, list.attr = nil, nil, list.attr and false
+ if list.height ~= nil and list.depth ~= nil then list.head = nil end
+ if ( disp and oper == 1 or oper == 2 ) and node.has_attribute( nd, attr_limits ) then
+ list.sub, list.sup = nil, nil
+ end
+ return pairs( list )
+end
+
+-- Tables representing special delimiters.
+local delim_null = { small_fam = 0, small_char = 0, large_fam = 0, large_char = 0 }
+local delim_auto = { small_fam = 0xF, small_char = 0xEF, large_fam = 0xE, large_char = 0xFE }
+
+-- Check if two delimiters (either real nodes or tables) are equal.
+local function delim_eq( da, db )
+ for i in pairs( delim_null ) do if da[i] ~= db[i] then return false end end
+ return true
+end
+
+-- Make the delimiter node or table da equal to db.
+local function delim_set( da, db )
+ for i in pairs( delim_null ) do da[i] = db[i] end
+ return da
+end
+
+-- Metatable for delimiter sequences. Internalises the overflow check.
+local meta_delims = {}
+function meta_delims.__index( tab, ix )
+ if type( ix ) ~= 'number' then return nil end
+ if ix > #tab then ix = #tab end
+ if ix < 1 then ix = 1 end
+ return rawget( tab, ix )
+end
+
+-- Stack to store auto delimiters.
+-- The last entry in this table is the current one.
+-- It contains two tables "opn" and "cls".
+-- Each contains a sequence of delimiter data for each level.
+local delimiters = {}
+
+-- Scans a sample of auto delimiters.
+-- Inserts the opening and closing delimites into the "opn" and "cls" tables in "tab".
+local function scan_sample( head, tab )
+ local opn, cls
+ for pos in node.traverse( head ) do
+ if is_fence( pos ) == 1 then opn = delim_set( {}, pos.delim )
+ elseif is_fence( pos ) == 3 then cls = delim_set( {}, pos.delim )
+ elseif is_noad( pos ) == 9 then scan_sample( pos.nucleus.head, tab ) end
+ end
+ if opn and cls then table.insert( tab.opn, opn ) table.insert( tab.cls, cls ) end
+end
+
+-- This function is triggered by a "set" command.
+-- The "sample" is a node containing a nested list if delimiter groups.
+-- Push a new item on the stack and scan the sample.
+local function set_auto( sample )
+ local tab = { opn = setmetatable( {}, meta_delims ), cls = setmetatable( {}, meta_delims ) }
+ table.insert( delimiters, tab )
+ scan_sample( sample, tab )
+end
+
+-- This function is triggered by a "res" command.
+-- Remove the topmost item from the stack.
+local function res_auto()
+ table.remove( delimiters )
+end
+
+-- Adapt a fake delimiter group to the given data.
+-- "head" is the node containing the "\math___{}" object.
+-- "level" is the nesting level to be used if this is an auto delimiter.
+-- "ht" and "dp" are the height and depth of the content of the group.
+-- Returns the level if this was an auto delimiter
+-- or a fixed one equal to the auto delimiter of some level.
+local function set_delim( head, level, ht, dp )
+ local type, delim, hbox = head.subtype == 6 and 'opn' or head.subtype == 7 and 'cls'
+ while is_noad( head ) do
+ node.unset_attribute( head, attr_info ) head = head.nucleus
+ node.unset_attribute( head, attr_info ) head = head.head
+ end
+ for pos in node.traverse( head ) do
+ node.unset_attribute( pos, attr_info )
+ if is_fence( pos ) == 3 then delim = pos.delim end
+ if is_noad( pos ) == 0 then hbox = pos.nucleus.head end
+ end
+ if not ( delim and hbox ) then return end
+ local auto = delim_eq( delim, delim_auto )
+ local brks = delimiters[#delimiters]
+ if brks then brks = brks[type] end
+ if type and auto and brks then
+ delim_set( delim, brks[level] )
+ elseif type and brks then
+ level = nil
+ for l = 1, #brks do if delim_eq( brks[l], delim ) then level = l break end end
+ else
+ level = nil
+ end
+ local scale = node.get_attribute( hbox, attr_info )
+ hbox.height, hbox.depth = ht * scale // 1000, dp * scale // 1000
+ if delim_eq( delim, delim_null ) then hbox.width = hbox.width * 2 end
+ return level
+end
+
+-- This creates a deep copy of the node list from start to stop (inclusive).
+-- Ignores whatsits and the content of nodes that have fixed dimension.
+-- Ignores limits of large operators if flagged and "disp" is true.
+-- Ignores nodes that have the info attribute set. These are unprocessed delimiters.
+-- Tries to keep track of the current math style.
+local function copy_list( start, stop, disp )
+ local old, new, copy, last = start
+ while old do
+ if is_style( old ) then disp = old.style:match( 'display' ) end
+ local ign = is_noad( old ) and node.has_attribute( old, attr_info ) or is_whatsit( old )
+ if not ign then
+ new = node.new( old.id, old.subtype )
+ for field, deep in fields( old, disp ) do
+ if deep then
+ local disp = disp and ( field == 'nucleus' or field == 'head' or field == 'display' )
+ new[field] = copy_list( old[field], nil, disp )
+ else
+ new[field] = old[field]
+ end
+ end
+ if not copy then last, copy = new, new
+ else last, copy = new, node.insert_after( copy, last, new ) end
+ end
+ if old == stop then break end
+ old = node.next( old )
+ end
+ return copy
+end
+
+-- Creates a math style node.
+local function style_node( style )
+ local nd = node.new( "style" )
+ nd.style = style
+ return nd
+end
+
+-- Copies the node list from "start" to "stop" and packs it into a temporary hbox.
+-- Returns the height and depth of that box when typeset in "style".
+local function dimensions( start, stop, style )
+ local disp = style == 'display' or style == 0 or style == 1
+ local copy = copy_list( start, stop, disp )
+ if not copy then return 0, 0 end
+ if style then copy = node.insert_before( copy, copy, style_node( style ) ) end
+ local box = node.mlist_to_hlist( copy, 'text', false )
+ if not box then return 0, 0 end
+ local wd, ht, dp = node.dimensions( box )
+ node.flush_list( box )
+ return ht, dp
+end
+
+-- Table containing information read from the aux file.
+local oldgroups = {}
+-- Table containing information to be written to the aux file.
+local newgroups = {}
+-- Table containing tables of tags that are synonyms for the same group.
+local equals = {}
+
+-- Merge information of a group with the inforation from the aux file
+-- and store the new information to be written to the aux file.
+-- A group table contains the following information:
+-- "tags": table of tags attached to the group (as keys with value "true").
+-- "ht", "dp": dimensions
+-- "lv": the maximal level of automatic delimiters used for subgroups.
+local function max( a, b ) return a and b and math.max( a, b ) or a or b end
+local function merge( group )
+ local tags = group.tags
+ if not tags or not next( tags ) then return end
+ if next( tags, ( next( tags ) ) ) then table.insert( equals, tags ) end
+ for tag in pairs( tags ) do
+ local ngrp = newgroups[tag] or {}
+ newgroups[tag] = ngrp
+ ngrp.ht = max( ngrp.ht, group.ht )
+ ngrp.dp = max( ngrp.dp, group.dp )
+ ngrp.lv = max( ngrp.lv, group.lv )
+ end
+ for tag in pairs( tags ) do
+ local ogrp = oldgroups[tag]
+ if ogrp then
+ group.ht = max( group.ht, ogrp.ht )
+ group.dp = max( group.dp, ogrp.dp )
+ group.lv = max( group.lv, ogrp.lv )
+ end
+ end
+end
+
+
+-- Parses a math list and process all delimiters.
+-- "pargrp" is the parent group.
+-- "head" is the head of the math list.
+-- "open" is the opening delimiter if this is a recursive call.
+-- "pos" is the position where to start the scan.
+-- "style" is the current math style, if known.
+-- This will otherwise be set when th first command is detected.
+-- "group" is the group table for this group.
+-- Returns a modified head, and sets information in the parent group.
+-- "delims" collects all delimiters belonging to this group.
+-- When a longmath special is detected, it is removed and the next node is processed:
+-- set/res: set or reset auto delimiters.
+-- opn: an opening delimiter follows. The function is called recursively.
+-- cls: a closing delimiter follows. The current group ends
+-- mid: an inner delimiter follows.
+-- For every other node in the list, its subnodes are parsed recursively.
+local function parse( pargrp, head, open, pos, style, group )
+ if not head then return end
+ local delims, subgrp = { open }
+ group = group or {}
+ pos = pos or head
+ while pos do
+ local comm, data, info = get_comm( pos )
+ if comm then head, pos = node.remove( head, pos )
+ if comm == 'set' then set_auto( pos ) return head end
+ if comm == 'res' then res_auto() return head end
+ style = style or info
+ group.tags = group.tags or {}
+ if comm == 'opn' then
+ local subgrp = { tags = {} }
+ if data then subgrp.tags[data] = true end
+ head, pos = parse( group, head, pos, node.next( pos ), info, subgrp )
+ elseif comm == 'mid' then
+ if data then group.tags[data] = true end
+ table.insert( delims, pos )
+ pos = node.next( pos )
+ elseif comm == 'cls' then
+ if data then group.tags[data] = true end
+ table.insert( delims, pos )
+ break
+ end
+ else
+ for field, deep in fields( pos ) do if deep then
+ pos[field] = parse( group, pos[field] )
+ end end
+ pos = node.next( pos )
+ end
+ end
+ -- if this is an actual delimiter group, measure its demensions.
+ if group.tags then group.ht, group.dp = dimensions( open or head, pos, style ) end
+ -- merge information with aux file
+ merge( group )
+ -- finally set the delimiters and parse any scripts attached to them.
+ local level, auto = group.lv or 0
+ for _, del in ipairs( delims ) do
+ auto = set_delim( del, level+1, group.ht, group.dp ) or auto
+ del.sub = parse( group, del.sub )
+ del.sup = parse( group, del.sup )
+ end
+ if pos and not open then
+ -- this group started without an opening delimiter.
+ -- we use a tail call and proceed as if there was one and this function was called from a parent.
+ pargrp = { lv = auto or group.lv, tags = {} }
+ if group.tags then for tag in pairs( group.tags ) do pargrp.tags[tag..parent] = true end end
+ return parse( {}, head, nil, node.next( pos ), style, pargrp )
+ else
+ -- inform the parent about the auto delimiter level and any tags used in subgroups.
+ pargrp.lv = max( pargrp.lv, auto or group.lv )
+ if pargrp.tags and group.tags then for tag in pairs( group.tags ) do pargrp.tags[tag..parent] = true end end
+ return head, pos
+ end
+end
+
+-- Callback that scans a math list.
+local function scan( head, style, pen )
+ head = parse( {}, head, nil, nil, style )
+ return node.mlist_to_hlist( head, style, true )
+end
+luatexbase.add_to_callback( 'mlist_to_hlist', scan, 'longmath parse' )
+
+-- Creates a glue node.
+local function glue_node( wd )
+ local nd = node.new( "glue", 8 )
+ nd.width = wd
+ return nd
+end
+
+-- Applies a stepwise shift to the hlists in a vlist if "extra" is non-zero.
+local function shift( head )
+ local extra = tex.dimen['longmath@extra']
+ if extra == 0 then return true end
+ local lines = {}
+ for nd in node.traverse( head ) do
+ if is_hlist( nd ) == 1 then table.insert( lines, nd ) end
+ end
+ local n = #lines - 1
+ if n > 0 then
+ for i, nd in ipairs( lines ) do
+ nd.width = nd.width + extra
+ nd.head = node.insert_before( nd.head, nd.head, glue_node( (i-1) * extra / n ) )
+ nd.head = node.insert_after( nd.head, node.tail( nd.head ), glue_node( (n-i+1) * extra / n ) )
+ end
+ end
+ return true
+end
+luatexbase.add_to_callback( 'post_linebreak_filter', shift, 'longmath shift' )
+
+-- Table for functions to be called from TeX.
+longmath = {}
+
+-- Read data for a collection of identical groups.
+function longmath.read_group( tags, tab )
+ for _, tag in ipairs( tags ) do oldgroups[tag] = tab end
+end
+
+-- Check if a tag is not already in a set.
+-- If a subgroup or parent is in the set, inserts it into a the "loops" table.
+local loops = {}
+local loop_patt = '[' .. parent .. ']*$'
+local function is_new( tag, set )
+ if set[tag] then return false end
+ tag = tag:gsub( loop_patt, '' )
+ for tagg in pairs( set ) do if tag == tagg:gsub( loop_patt, '' ) then
+ loops[ tag:gsub( '@$', '' ) ] = true
+ return false
+ end end
+ return true
+end
+
+-- Given a set of tags attached to the same group,
+-- add all other tags attached to the same group to the set.
+local function find_eq( set )
+ local new = {}
+ for tag in pairs( set ) do
+ local app = ''
+ while true do
+ for _, w in ipairs( equals ) do if w[tag] then for tagg in pairs( w ) do
+ if is_new( tagg..app, set ) then new[tagg..app] = true end
+ end end end
+ if tag:sub(-1) ~= parent then break end
+ tag, app = tag:sub(1,-2), app .. parent
+ end
+ end
+ if not next( new ) then return set end
+ for tag in pairs( new ) do set[tag] = true end
+ return find_eq( set )
+end
+
+local loop_mess = '\\PackageWarningNoLine{longmath}{Cyclic delimiter group%s %s detected}'
+local data_mess = '\\PackageWarningNoLine{longmath}{Delimiters may have changed. Rerun to get them right}'
+
+-- Save all groups to the aux file.
+-- Tags that belong to the same group are collected into a single entry.
+function longmath.save_groups( aux )
+ local check, done = true, {}
+ for tag, grp in pairs( newgroups ) do if not done[tag] then
+ local tags, vals, eqs = {}, {}, find_eq( { [tag] = true } )
+ for tagg in pairs( eqs ) do
+ check, done[tagg] = check and oldgroups[tagg], true
+ table.insert( tags, string.format( '%q', tagg ) )
+ while tagg do
+ if newgroups[tagg] then
+ grp.ht, grp.dp = max( grp.ht, newgroups[tagg].ht ), max( grp.dp, newgroups[tagg].dp )
+ grp.lv = max( grp.lv, newgroups[tagg].lv )
+ end
+ tagg = tagg:match( '^(.*)[' .. parent .. ']$' )
+ end
+ end
+ for k, v in pairs( grp ) do
+ table.insert( vals, string.format( '%s=%d', k, v ) )
+ for tagg in pairs( eqs ) do check = check and v == oldgroups[tagg][k] end
+ end
+ tags, vals = table.concat( tags, ',' ), table.concat( vals, ',' )
+ texio.write( aux, string.format( '\\longmath@group{%s}{%s}\n', tags, vals ) )
+ end end
+ local lps = {}
+ for l in pairs( loops ) do
+ if #lps > 5 then table.insert( lps, '...' ) break end
+ table.insert( lps, l )
+ end
+ if #lps > 0 then
+ tex.print( string.format( loop_mess, #lps > 1 and 's' or '', table.concat( lps, ', ' ) ) )
+ end
+ if not check then tex.print( data_mess ) end
+end
+
+return
diff --git a/macros/luatex/latex/longmath/longmath.sty b/macros/luatex/latex/longmath/longmath.sty
new file mode 100644
index 0000000000..2b4848db3c
--- /dev/null
+++ b/macros/luatex/latex/longmath/longmath.sty
@@ -0,0 +1,223 @@
+%%
+%% longmath.sty is part of longmath version 0.1.
+%%
+%% (c) 2024 Hans-Jürgen Matschull
+%%
+%% 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 Hans-Jürgen Matschull
+%%
+%% see README for a list of files belonging to longmath.
+%%
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{longmath}[2024/03/25]
+
+\RequirePackage{luatexbase}
+
+% generic attribute used to mark special objects and send mathstyle to Lua.
+\newattribute\longmath@info
+
+% attribute to tag large operators.
+\newattribute\longmath@limits
+\def\ignorelimits{\setattribute{\longmath@limits}{1}}
+\def\includelimits{\unsetattribute{\longmath@limits}}
+
+% Save original primitives.
+\let \longmath@left \left
+\let \longmath@middle \middle
+\let \longmath@right \right
+
+\directlua{ require( 'longmath.lua' ) }
+
+% Registers.
+\newcount\delimiterscale \delimiterscale 902
+\newdimen\longmathlinesep \longmathlinesep 3pt
+
+\newif\iflongmath@star
+\newcount\longmath@lines
+\newdimen\longmath@extra
+\newdimen\longmath@width
+\newcount\longmath@xscale
+\newcount\longmath@yscale
+\newbox\longmath@box
+
+% The prefix for tags.
+\def\longmath@prefix{}
+\def\delimiterprefix#1{\def\longmath@prefix{#1}}
+
+% User macros
+\def\lleft {\longmath@delimA \mathopen {1000}{opn}}
+\def\rright {\longmath@delimA \mathclose {1000}{cls}}
+\def\mleft {\longmath@delimA \mathopen {1000}{mid}}
+\def\mright {\longmath@delimA \mathclose {1000}{mid}}
+\def\mmord {\longmath@delimA \mathord \delimiterscale {mid}}
+\def\mmbin {\longmath@delimA \mathbin \delimiterscale {mid}}
+\def\mmrel {\longmath@delimA \mathrel \delimiterscale {mid}}
+\def\mmpunct{\longmath@delimA \mathpunct \delimiterscale {mid}}
+\def\mmiddle{\longmath@delimA \mathinner \delimiterscale {mid}}
+\def\mmop {\longmath@delimA \mathop \delimiterscale {mid}}
+
+% This sets the math atom type, the default scale factor, and the command to be send to Lua,
+% then checks if a tag in {} is following.
+\def\longmath@delimA #1#2#3{\begingroup
+ \let\longmath@type #1 \longmath@xscale = #2 \relax \edef\longmath@comm{#3}
+ \futurelet \longmath@token \longmath@delimB }
+
+% If a tag is following, read it. Otherwise, skip this step.
+\def\longmath@delimB{\ifx\longmath@token\bgroup \expandafter \longmath@delimC
+ \else \expandafter \longmath@delimD \fi }
+
+% Read the tag, prepend the prefix and append a @, then add it to the command.
+\def\longmath@delimC #1{\edef\longmath@comm{\longmath@comm:\longmath@prefix#1@} \longmath@delimD }
+
+% Read the optional scale factor. If none is given, the scale is set to 0.
+\def\longmath@delimD #1{\afterassignment\longmath@delimE \longmath@yscale = 0#1}
+
+% This creates a \special whatsit with the command as string argument, followed by
+% a fake delimiter group with the requested delimiter always being the right one.
+% For the delimiter nodes, the info attribute is set to the current math style.
+% For the empty \hbox, it is set to the requested scale factor.
+\def\longmath@delimE{
+ \setattribute\longmath@info{\mathstyle} \special{\longmath@comm}
+ \longmath@type\bgroup \longmath@left . \aftergroup\egroup \aftergroup\endgroup
+ \begingroup \setattribute\longmath@info{\ifnum \longmath@yscale < 1 \longmath@xscale \else \longmath@yscale \fi}
+ \hbox to -\nulldelimiterspace{} \endgroup
+ \longmath@right }
+
+
+% magic number to identify the variable delimiter.
+\delcode`* = "FEFEFE %"
+
+% scanner to read the delimiter sample and create a nested \left-\right-group.
+\def\longmath@autoL #1#2\relax{\ifx *#1 \longmath@autoR #2\relax
+ \else \longmath@left #1 \longmath@autoL #2\relax \fi }
+\def\longmath@autoR #1#2\relax{\ifx *#1
+ \else \longmath@right #1 \longmath@autoR #2\relax \fi}
+
+% Read the sample provided by the user, insert a set command, and typeset it into a box.
+\def\autodelimiters #1{\begingroup \longmath@info = 0
+ \setbox\longmath@box\hbox{$ \special{set} \longmath@autoL #1***\relax $}
+ \endgroup\aftergroup\longmath@autoreset}
+
+% To make the setting local to the current TeX group, send a reset command at the end of the group.
+\def\longmath@autoreset{\begingroup \longmath@info = 0
+ \setbox\longmath@box\hbox{$ \special{res} {} $}
+ \endgroup}
+
+
+% Counter for automatic tag generation.
+\newcount\longmath@tagnum
+
+% Stack to store pushed tags, separated by spaces.
+\def\longmath@stack{}
+
+% Create a unique tag.
+\def\longmath@unique{
+ \global\advance\longmath@tagnum\@ne
+ \global\edef\longmath@stack{@\the\longmath@tagnum\space\longmath@stack}
+}
+
+% Create an invisible closing delimiter.
+\def\pushdelimiter{\longmath@unique
+ \expandafter\longmath@push\longmath@stack\relax}
+
+\def\longmath@push #1 #2\relax{\ifmmode\begingroup
+ \def\longmath@comm{mid:#1} \let\longmath@type\mathclose \longmath@yscale=1\relax
+ \longmath@delimE . \fi}
+
+% Create an invisible opening delimiter.
+\def\pulldelimiter{\ifx\longmath@stack\empty \longmath@unique \fi
+ \expandafter\longmath@pull\longmath@stack\relax}
+
+\def\longmath@pull #1 #2\relax{\global\edef\longmath@stack{#2}\ifmmode\begingroup
+ \def\longmath@comm{mid:#1} \let\longmath@type\mathopen \longmath@yscale=1\relax
+ \longmath@delimE . \fi}
+
+
+% Read width argument for the longmath environment.
+% Expects to numbers or dimensions terminted by +.
+% Numbers are multplied by \hsize.
+\def\longmath@gobble #1\relax{}
+\def\longmath@setwidth #1+#2+#3\relax{
+ \afterassignment\longmath@gobble \longmath@width = #1 \hsize \relax
+ \afterassignment\longmath@gobble \longmath@extra = #2 \hsize \relax }
+
+% Alignment for the longmath box.
+\let\longmath@align@t@\vtop
+\let\longmath@align@b@\vbox
+\let\longmath@align@c@\vcenter
+
+% Save and restore the current math style.
+\newcount\longmath@style
+\def\longmath@getstyle{\longmath@style = \mathstyle \relax}
+\def\longmath@setstyle{\ifcase\longmath@style \relax \displaystyle \or \crampeddisplaystyle \or
+ \textstyle \or \crampedtextstyle \or \scriptstyle \or \crampedscriptstyle \or
+ \scriptscriptstyle \or \crampedscriptscriptstyle \else \textstyle \fi }
+
+% Begin of longmath environment.
+% Set alignment and width and save the current math style.
+% Then start a horizontal box in math mode.
+\def\longmath@begin #1#2{\begingroup
+ \expandafter\let\expandafter\longmath@align\csname longmath@align@#1@\endcsname
+ \ifx \longmath@align\relax \let\longmath@align\longmath@align@c@ \fi
+ \longmath@getstyle \longmath@setwidth #2+0+\relax
+ %\rlap{\textcolor{green}{\rule{\longmath@width}{0.1pt}}\textcolor{yellow}{\rule{\longmath@extra}{0.1pt}}}
+ \setbox\longmath@box\hbox\bgroup $ \mathpenaltiesmode = 1 \longmath@setstyle
+}
+
+% End of longmath environment.
+% If the width of the box is smaller than the requsted width, just use it as is.
+% Otherwise, modify the requested width if the * version was used.
+% Then \unhbox the content into a paragraph with the requested width.
+% \box0 and \box1 are used to adapt the baselineskip to the math style.
+% \leftskip and \rightskip are set such that lines are centered but not justified.
+% The glue is removed from the beginning of the first line with \hskip,
+% and from the end of the last line with \parfilskip.
+% If \longmath@extra is non-zero, Lua will stack the lines of the paragraph.
+\def\longmath@end{$\egroup
+ \ifdim \wd\longmath@box < \dimexpr \longmath@width + \longmath@extra \relax
+ \longmath@extra = \z@
+ \box\longmath@box
+ \else
+ \iflongmath@star
+ \longmath@lines = \dimexpr ( 1.1\wd\longmath@box + 0.5\longmath@width ) / \longmath@width \relax
+ \longmath@width = \dimexpr ( 1.1\wd\longmath@box / \longmath@lines )
+ \fi
+ \setbox0\hbox{$1$}
+ \setbox1\hbox{$\longmath@setstyle 1$}
+ \longmath@align{\hsize\longmath@width
+ \baselineskip \dimexpr \baselineskip * \ht1 / \ht0 \relax \lineskip\z@ \lineskiplimit\z@
+ \openup \longmathlinesep
+ \leftskip 0pt plus 0.6\longmath@width
+ \rightskip 0pt plus 0.6\longmath@width
+ \parfillskip-\rightskip \noindent\hskip-\leftskip \unhbox\longmath@box}
+ \longmath@extra = \z@
+ \fi \endgroup
+}
+
+\newenvironment{longmath}[2][c]
+ {\longmath@starfalse\longmath@begin{#1}{#2}}{\longmath@end}
+\newenvironment{longmath*}[2][c]
+ {\longmath@startrue\longmath@begin{#1}{#2}}{\longmath@end}
+
+% Read the dimensions of tagged groups from the aux file.
+\def\longmath@group#1#2{\directlua{longmath.read_group({#1},{#2})}}
+\def\longmath@skip#1#2{}
+
+% Save the dimensions of the tagged groups at the end of the document.
+\def\longmath@save{\let\longmath@group\longmath@skip\directlua{longmath.save_groups(\the\@auxout)}}
+\AtEndDocument{\longmath@save}
+
+
+
+
+
+