% language=us runpath=texruns:manuals/lowlevel \startcomponent lowlevel-alignments \environment lowlevel-style \startdocument [title=alignments, color=middlegreen] \startsectionlevel[title=Introduction] \TEX\ has a couple of subsystems and alignments is one of them. This mechanism is used to construct tables or alike. Because alignments use low level primitives to set up and construct a table, and because such a setup can be rather extensive, in most cases users will rely on macros that hide this. \startbuffer \halign { \alignmark\hss \aligntab \hss\alignmark\hss \aligntab \hss\alignmark \cr 1.1 \aligntab 2,2 \aligntab 3=3 \cr 11.11 \aligntab 22,22 \aligntab 33=33 \cr 111.111 \aligntab 222,222 \aligntab 333=333 \cr } \stopbuffer \typebuffer[option=TEX] That one doesn't look too complex and comes out as: \blank\getbuffer\blank This is how the previous code comes out when we use one of the \CONTEXT\ table mechanism. \startbuffer \starttabulate[|l|c|r|] \NC 1.1 \NC 2,2 \NC 3=3 \NC \NR \NC 11.11 \NC 22,22 \NC 33=33 \NC \NR \NC 111.111 \NC 222,222 \NC 333=333 \NC \NR \stoptabulate \stopbuffer \typebuffer[option=TEX] \blank\getbuffer\blank That one looks a bit different with respect to spaces, so let's go back to the low level variant: \startbuffer \halign { \alignmark\hss \aligntab \hss\alignmark\hss \aligntab \hss\alignmark \cr 1.1\aligntab 2,2\aligntab 3=3\cr 11.11\aligntab 22,22\aligntab 33=33\cr 111.111\aligntab 222,222\aligntab 333=333\cr } \stopbuffer \typebuffer[option=TEX] Here we don't have spaces in the content part and therefore also no spaces in the result: \blank\getbuffer\blank You can automate dealing with unwanted spacing: \startbuffer \halign { \ignorespaces\alignmark\unskip\hss \aligntab \hss\ignorespaces\alignmark\unskip\hss \aligntab \hss\ignorespaces\alignmark\unskip \cr 1.1 \aligntab 2,2 \aligntab 3=3 \cr 11.11 \aligntab 22,22 \aligntab 33=33 \cr 111.111 \aligntab 222,222 \aligntab 333=333 \cr } \stopbuffer \typebuffer[option=TEX] We get: \blank\getbuffer\blank By moving the space skipping and cleanup to the so called preamble we don't need to deal with it in the content part. We can also deal with inter|-|column spacing there: \startbuffer \halign { \ignorespaces\alignmark\unskip\hss \tabskip 1em \aligntab \hss\ignorespaces\alignmark\unskip\hss \tabskip 1em \aligntab \hss\ignorespaces\alignmark\unskip \tabskip 0pt \cr 1.1 \aligntab 2,2 \aligntab 3=3 \cr 11.11 \aligntab 22,22 \aligntab 33=33 \cr 111.111 \aligntab 222,222 \aligntab 333=333 \cr } \stopbuffer \typebuffer[option=TEX] \blank\getbuffer\blank If for the moment we forget about spanning columns (\type {\span}) and locally ignoring preamble entries (\type {\omit}) these basic commands are not that complex to deal with. Here we use \type {\alignmark} but that is just a primitive that we use instead of \type {#} while \type {\aligntab} is the same as \type {&}, but using the characters instead also assumes that they have the catcode that relates to a parameter and alignment tab (and in \CONTEXT\ that is not the case). The \TEX book has plenty alignment examples so if you really want to learn about them, consult that must|-|have|-|book. \stopsectionlevel \startsectionlevel[title=Between the lines] The individual rows of a horizontal alignment are treated as lines. This means that, as we see in the previous section, the interline spacing is okay. However, that also means that when we mix the lines with rules, the normal \TEX\ habits kick in. Take this: \startbuffer \halign { \ignorespaces\alignmark\unskip\hss \tabskip 1em \aligntab \hss\ignorespaces\alignmark\unskip\hss \tabskip 1em \aligntab \hss\ignorespaces\alignmark\unskip \tabskip 0pt \cr \noalign{\hrule} 1.1 \aligntab 2,2 \aligntab 3=3 \cr \noalign{\hrule} 11.11 \aligntab 22,22 \aligntab 33=33 \cr \noalign{\hrule} 111.111 \aligntab 222,222 \aligntab 333=333 \cr \noalign{\hrule} } \stopbuffer \typebuffer[option=TEX] The result doesn't look pretty and actually, when you see documents produced by \TEX\ using alignments you should not be surprised to notice rather ugly spacing. The user (or the macropackage) should deal with that explicitly, and this is not always the case. \startlinecorrection \getbuffer \stoplinecorrection The solution is often easy: \startbuffer \halign { \ignorespaces\strut\alignmark\unskip\hss \tabskip 1em \aligntab \hss\ignorespaces\strut\alignmark\unskip\hss \tabskip 1em \aligntab \hss\ignorespaces\strut\alignmark\unskip \tabskip 0pt \cr \noalign{\hrule} 1.1 \aligntab 2,2 \aligntab 3=3 \cr \noalign{\hrule} 11.11 \aligntab 22,22 \aligntab 33=33 \cr \noalign{\hrule} 111.111 \aligntab 222,222 \aligntab 333=333 \cr \noalign{\hrule} } \stopbuffer \typebuffer[option=TEX] \startlinecorrection \getbuffer \stoplinecorrection The user will not notice it but alignments put some pressure on the general \TEX\ scanner. Actually, the scanner is either scanning an alignment or it expects regular text (including math). When you look at the previous example you see \type {\noalign}. When the preamble is read, \TEX\ will pick up rows till it finds the final brace. Each row is added to a temporary list and the \type {\noalign} will enter a mode where other stuff gets added to that list. It all involves subtle look ahead but with minimal overhead. When the whole alignment is collected a final pass over that list will package the cells and rows (lines) in the appropriate way using information collected (like the maximum width of a cell and width of the current cell. It will also deal with spanning cells then. So let's summarize what happens: \startitemize[n,packed] \startitem scan the preamble that defines the cells (where the last one is repeated when needed) \stopitem \startitem check for \type {\cr}, \type {\noalign} or a right brace; when a row is entered scan for cells in parallel the preamble so that cell specifications can be applied (then start again) \stopitem \startitem package the preamble based on information with regards to the cells in a column \stopitem \startitem apply the preamble packaging information to the columns and also deal with pending cell spans \stopitem \startitem flush the result to the current list, unless packages in a box a \type {\halign} is seen as paragraph and rows as lines (such a table can split) \stopitem \stopitemize The second (repeated) step is complicated by the fact that the scanner has to look ahead for a \type {\noalign}, \type {\cr}, \type {\omit} or \type {\span} and when doing that it has to expand what comes. This can give side effects and often results in obscure error messages. When for instance an \type {\if} is seen and expanded, the wrong branch can be entered. And when you use protected macros embedded alignment commands are not seen at all; of course they still need to produce valid operations in the current context. All these side effects are to be handled in a macro package when it wraps alignments in a high level interface and \CONTEXT\ does that for you. But because the code doesn't always look pretty then, in \LUAMETATEX\ the alignment mechanism has been extended a bit over time. Nesting \type {\noalign} is normally not permitted (but one can redefine this primitive such that a macro package nevertheless handles it). The first extension permits nested usage of \type {\noalign}. This has resulted of a little reorganization of the code. A next extension showed up when overload protection was introduced and extra prefixes were added. We can signal the scanner that a macro is actually a \type {\noalign} variant: \footnote {One can argue for using the name \type {\peekaligned} because in the meantime other alignment primitives also can use this property.} \starttyping[option=TEX] \noaligned\protected\def\InBetween{\noalign{...}} \stoptyping Here the \type {\InBetween} macro will get the same treatment as \type {\noalign} and it will not trigger an error. This extension resulted in a second bit of reorganization (think of internal command codes and such) but still the original processing of alignments was there. A third overhaul of the code actually did lead to some adaptations in the way alignments are constructed so let's move on to that. \stopsectionlevel \startsectionlevel[title={Pre-, inter- and post-tab skips}] The basic structure of a preamble and row is actually not that complex: it is a mix of tab skip glue and cells (that are just boxes): \startbuffer \tabskip 10pt \halign { \strut\alignmark\tabskip 12pt\aligntab \strut\alignmark\tabskip 14pt\aligntab \strut\alignmark\tabskip 16pt\cr \noalign{\hrule} cell 1.1\aligntab cell 1.2\aligntab cell 1.3\cr \noalign{\hrule} cell 2.1\aligntab cell 2.2\aligntab cell 2.3\cr \noalign{\hrule} } \stopbuffer \typebuffer[option=TEX] The tab skips are set in advance and apply to the next cell (or after the last one). \startbuffer[blownup-1] \startlinecorrection {\showmakeup[glue]\scale[width=\textwidth]{\vbox{\getbuffer}}} \stoplinecorrection \stopbuffer \getbuffer[blownup-1] % \normalizelinemode \zerocount % \discardzerotabskipsnormalizecode In the \CONTEXT\ table mechanisms the value of \type {\tabskip} is zero in most cases. As in: \startbuffer \tabskip 0pt \halign { \strut\alignmark\aligntab \strut\alignmark\aligntab \strut\alignmark\cr \noalign{\hrule} cell 1.1\aligntab cell 1.2\aligntab cell 1.3\cr \noalign{\hrule} cell 2.1\aligntab cell 2.2\aligntab cell 2.3\cr \noalign{\hrule} } \stopbuffer \typebuffer[option=TEX] When these ships are zero, they still show up in the end: \getbuffer[blownup-1] Normally, in order to achieve certain effects there will be more align entries in the preamble than cells in the table, for instance because you want vertical lines between cells. When these are not used, you can get quite a bit of empty boxes and zero skips. Now, of course this is seldom a problem, but when you have a test document where you want to show font properties in a table and that font supports a script with some ten thousand glyphs, you can imagine that it accumulates and in \LUATEX\ (and \LUAMETATEX) nodes are larger so it is one of these cases where in \CONTEXT\ we get messages on the console that node memory is bumped. \footnote {I suppose it was a coincidence that a few weeks after these features came available a user consulted the mailing list about a few thousand page table that made the engine run out of memory, something that could be cured by enabling these new features.} After playing a bit with stripping zero tab skips I found that the code would not really benefit from such a feature: lots of extra tests made it quite ugly. As a result a first alternative was to just strip zero skips before an alignment got flushed. At least we're then a bit leaner in the processes that come after it. This feature is now available as one of the normalizer bits. But, as we moved on, a more natural approach was to keep the skips in the preamble, because that is where a guaranteed alternating skip|/|box is assumed. It also makes that the original documentation is still valid. However, in the rows construction we can be lean. This is driven by a keyword to \type {\halign}: \startbuffer \tabskip 0pt \halign noskips { \strut\alignmark\aligntab \strut\alignmark\aligntab \strut\alignmark\cr \noalign{\hrule} cell 1.1\aligntab cell 1.2\aligntab cell 1.3\cr \noalign{\hrule} cell 2.1\aligntab cell 2.2\aligntab cell 2.3\cr \noalign{\hrule} } \stopbuffer \typebuffer[option=TEX] No zero tab skips show up here: \getbuffer[blownup-1] When playing with all this the \LUAMETATEX\ engine also got a tracing option for alignments. We already had one that showed some of the \type{\noalign} side effects, but showing the preamble was not yet there. This is what \typ {\tracingalignments = 2} results in: % {\tracingalignments2 \setbox0\vbox{\getbuffer}} \starttyping[option=TEX] \glue[ignored][...] 0.0pt \alignrecord ..{\strut } .. ..{\endtemplate } \glue[ignored][...] 0.0pt \alignrecord ..{\strut } .. ..{\endtemplate } \glue[ignored][...] 0.0pt \alignrecord ..{\strut } .. ..{\endtemplate } \glue[ignored][...] 0.0pt \stoptyping The \type {ignored} subtype is (currently) only used for these alignment tab skips and it triggers a check later on when the rows are constructed. The \type {} is what get injected in the cell (represented by \type {\alignmark}). The pseudo primitives are internal and not public. \stopsectionlevel \startsectionlevel[title={Cell widths}] Imagine this: \startbuffer \halign { x\hbox to 3cm{\strut \alignmark\hss}\aligntab x\hbox to 3cm{\strut\hss\alignmark\hss}\aligntab x\hbox to 3cm{\strut\hss\alignmark }\cr cell 1.1\aligntab cell 1.2\aligntab cell 1.3\cr cell 2.1\aligntab cell 2.2\aligntab cell 2.3\cr } \stopbuffer \typebuffer[option=TEX] which renders as: \startbuffer[blownup-2] \startlinecorrection {\showboxes\scale[width=\textwidth]{\vbox{\getbuffer}}} \stoplinecorrection \stopbuffer {\showboxes\getbuffer[blownup-2]} A reason to have boxes here is that it enforces a cell width but that is done at the cost of an extra wrapper. In \LUAMETATEX\ the \type {hlist} nodes are rather large because we have more options than in original \TEX, for instance offsets and orientation. In a table with 10K rows of 4 cells yet get 40K extra \type {hlist} nodes allocated. Now, one can argue that we have plenty of memory but being lazy is not really a sign of proper programming. \startbuffer \halign { x\tabsize 3cm\strut \alignmark\hss\aligntab x\tabsize 3cm\strut\hss\alignmark\aligntab x\tabsize 3cm\strut\hss\alignmark\hss\cr cell 1.1\aligntab cell 1.2\aligntab cell 1.3\cr cell 2.1\aligntab cell 2.2\aligntab cell 2.3\cr } \stopbuffer \typebuffer[option=TEX] If you look carefully you will see that this time we don't have the embedded boxes: {\showboxes\getbuffer[blownup-2]} So, both the sparse skip and new \type {\tabsize} feature help to make these extreme tables (spanning hundreds of pages) not consume irrelevant memory and also make that later on we don't have to consult useless nodes. \stopsectionlevel \startsectionlevel[title=Plugins] Yet another \LUAMETATEX\ extension is a callback that kicks in between the preamble preroll and finalizing the alignment. Initially as test and demonstration a basic character alignment feature was written but that works so well that in some places it can replace (or compliment) the already existing features in the \CONTEXT\ table mechanisms. \startbuffer \starttabulate[|lG{.}|cG{,}|rG{=}|cG{x}|] \NC 1.1 \NC 2,2 \NC 3=3 \NC a 0xFF \NC \NR \NC 11.11 \NC 22,22 \NC 33=33 \NC b 0xFFF \NC \NR \NC 111.111 \NC 222,222 \NC 333=333 \NC c 0xFFFF \NC \NR \stoptabulate \stopbuffer \typebuffer[option=TEX] The tabulate mechanism in \CONTEXT\ is rather old and stable and it is the preferred way to deal with tabular content in the text flow. However, adding the \type {G} specifier (as variant of the \type {g} one) could be done without interference or drop in performance. This new \type {G} specifier tells the tabulate mechanism that in that column the given character is used to vertically align the content that has this character. \blank\getbuffer\blank Let's make clear that this is {\em not} an engine feature but a \CONTEXT\ one. It is however made easy by this callback mechanism. We can of course use this feature with the low level alignment primitives, assuming that you tell the machinery that the plugin is to be kicked in. \startbuffer \halign noskips \alignmentcharactertrigger \bgroup \tabskip2em \setalignmentcharacter.\ignorespaces\alignmark\unskip\hss \aligntab \hss\setalignmentcharacter,\ignorespaces\alignmark\unskip\hss \aligntab \hss\setalignmentcharacter=\ignorespaces\alignmark\unskip \aligntab \hss \ignorespaces\alignmark\unskip\hss \cr 1.1 \aligntab 2,2 \aligntab 3=3 \aligntab \setalignmentcharacter{.}\relax 4.4\cr 11.11 \aligntab 22,22 \aligntab 33=33 \aligntab \setalignmentcharacter{,}\relax 44,44\cr 111.111 \aligntab 222,222 \aligntab 333=333 \aligntab \setalignmentcharacter{!}\relax 444!444\cr x \aligntab x \aligntab x \aligntab \setalignmentcharacter{/}\relax /\cr .1 \aligntab ,2 \aligntab =3 \aligntab \setalignmentcharacter{?}\relax ?4\cr .111 \aligntab ,222 \aligntab =333 \aligntab \setalignmentcharacter{=}\relax 44=444\cr \egroup \stopbuffer {\switchtobodyfont[8pt] \typebuffer[option=TEX]} This rather verbose setup renders as: \blank\getbuffer\blank Using a high level interface makes sense but local control over such alignment too, so here follow some more examples. Here we use different alignment characters: \startbuffer \starttabulate[|lG{.}|cG{,}|rG{=}|cG{x}|] \NC 1.1 \NC 2,2 \NC 3=3 \NC a 0xFF \NC \NR \NC 11.11 \NC 22,22 \NC 33=33 \NC b 0xFFF \NC \NR \NC 111.111 \NC 222,222 \NC 333=333 \NC c 0xFFFF \NC \NR \stoptabulate \stopbuffer \typebuffer[option=TEX] \getbuffer In this example we specify the characters in the cells. We still need to add a specifier in the preamble definition because that will trigger the plugin. \startbuffer \starttabulate[|lG{}|rG{}|] \NC left \NC right \NC\NR \NC \showglyphs \setalignmentcharacter{.}1.1 \NC \setalignmentcharacter{.}1.1 \NC\NR \NC \showglyphs \setalignmentcharacter{,}11,11 \NC \setalignmentcharacter{,}11,11 \NC\NR \NC \showglyphs \setalignmentcharacter{=}111=111 \NC \setalignmentcharacter{=}111=111 \NC\NR \stoptabulate \stopbuffer {\switchtobodyfont[8pt] \typebuffer[option=TEX]} \getbuffer You can mix these approaches: \startbuffer \starttabulate[|lG{.}|rG{}|] \NC left \NC right \NC\NR \NC 1.1 \NC \setalignmentcharacter{.}1.1 \NC\NR \NC 11.11 \NC \setalignmentcharacter{.}11.11 \NC\NR \NC 111.111 \NC \setalignmentcharacter{.}111.111 \NC\NR \stoptabulate \stopbuffer \typebuffer[option=TEX] \getbuffer Here the already present alignment feature, that at some point in tabulate might use this new feature, is meant for numbers, but here we can go wild with words, although of course you need to keep in mind that we deal with typeset text, so there may be no match. \startbuffer \starttabulate[|lG{.}|rG{.}|] \NC foo.bar \NC foo.bar \NC \NR \NC oo.ba \NC oo.ba \NC \NR \NC o.b \NC o.b \NC \NR \stoptabulate \stopbuffer \typebuffer[option=TEX] \getbuffer This feature will only be used in know situations and those seldom involve advanced typesetting. However, the following does work: \footnote {Should this be an option instead?} \startbuffer \starttabulate[|cG{d}|] \NC \smallcaps abcdefgh \NC \NR \NC xdy \NC \NR \NC \sl xdy \NC \NR \NC \tttf xdy \NC \NR \NC \tfd d \NC \NR \stoptabulate \stopbuffer \typebuffer[option=TEX] \getbuffer As always with such mechanisms, the question is \quotation {Where to stop?} But it makes for nice demos and as long as little code is needed it doesn't hurt. \stopsectionlevel \startsectionlevel[title=Pitfalls and tricks] The next example mixes bidirectional typesetting. It might look weird at first sight but the result conforms to what we discussed in previous paragraphs. \startbuffer \starttabulate[|lG{.}|lG{}|] \NC \righttoleft 1.1 \NC \righttoleft \setalignmentcharacter{.}1.1 \NC\NR \NC 1.1 \NC \setalignmentcharacter{.}1.1 \NC\NR \NC \righttoleft 1.11 \NC \righttoleft \setalignmentcharacter{.}1.11 \NC\NR \NC 1.11 \NC \setalignmentcharacter{.}1.11 \NC\NR \NC \righttoleft 1.111 \NC \righttoleft \setalignmentcharacter{.}1.111 \NC\NR \NC 1.111 \NC \setalignmentcharacter{.}1.111 \NC\NR \stoptabulate \stopbuffer {\switchtobodyfont[8pt] \typebuffer[option=TEX]} \getbuffer In case of doubt, look at this: \startbuffer \starttabulate[|lG{.}|lG{}|lG{.}|lG{}|] \NC \righttoleft 1.1 \NC \righttoleft \setalignmentcharacter{.}1.1 \NC 1.1 \NC \setalignmentcharacter{.}1.1 \NC\NR \NC \righttoleft 1.11 \NC \righttoleft \setalignmentcharacter{.}1.11 \NC 1.11 \NC \setalignmentcharacter{.}1.11 \NC\NR \NC \righttoleft 1.111 \NC \righttoleft \setalignmentcharacter{.}1.111 \NC 1.111 \NC \setalignmentcharacter{.}1.111 \NC\NR \stoptabulate \stopbuffer {\switchtobodyfont[8pt] \typebuffer[option=TEX]} \getbuffer The next example shows the effect of \type {\omit} and \type {\span}. The first one makes that in this cell the preamble template is ignored. \startbuffer \halign \bgroup \tabsize 2cm\relax [\alignmark]\hss \aligntab \tabsize 2cm\relax \hss[\alignmark]\hss \aligntab \tabsize 2cm\relax \hss[\alignmark]\cr 1\aligntab 2\aligntab 3\cr \omit 1\aligntab \omit 2\aligntab \omit 3\cr 1\aligntab 2\span 3\cr 1\span 2\aligntab 3\cr 1\span 2\span 3\cr 1\span \omit 2\span \omit 3\cr \omit 1\span \omit 2\span \omit 3\cr \egroup \stopbuffer \typebuffer[option=TEX] Spans are applied at the end so you see a mix of templates applied. {\showboxes\getbuffer[blownup-2]} When you define an alignment inside a macro, you need to duplicate the \type {\alignmark} signals. This is similar to embedded macro definitions. But in \LUAMETATEX\ we can get around that by using \type {\aligncontent}. Keep in mind that when the preamble is scanned there is no doesn't expand with the exception of the token after \type {\span}. \startbuffer \halign \bgroup \tabsize 2cm\relax \aligncontent\hss \aligntab \tabsize 2cm\relax \hss\aligncontent\hss \aligntab \tabsize 2cm\relax \hss\aligncontent\cr 1\aligntab 2\aligntab 3\cr A\aligntab B\aligntab C\cr \egroup \stopbuffer \typebuffer[option=TEX] \blank\getbuffer\blank In this example we still have to be verbose in the way we align but we can do this: \startbuffer \halign \bgroup \tabsize 2cm\relax \aligncontentleft \aligntab \tabsize 2cm\relax \aligncontentmiddle\aligntab \tabsize 2cm\relax \aligncontentright \cr 1\aligntab 2\aligntab 3\cr A\aligntab B\aligntab C\cr \egroup \stopbuffer \typebuffer[option=TEX] Where the helpers are defined as: \starttyping[option=TEX] \noaligned\protected\def\aligncontentleft {\ignorespaces\aligncontent\unskip\hss} \noaligned\protected\def\aligncontentmiddle {\hss\ignorespaces\aligncontent\unskip\hss} \noaligned\protected\def\aligncontentright {\hss\ignorespaces\aligncontent\unskip} \stoptyping The preamble scanner see such macros as candidates for a single level expansion so it will inject the meaning and see the \type {\aligncontent} eventually. \blank\getbuffer\blank The same effect could be achieved by using the \type {\span} prefix: \starttyping[option=TEX] \def\aligncontentleft{\ignorespaces\aligncontent\unskip\hss} \halign { ... \span\aligncontentleft ...} \stoptyping One of the reasons for not directly using the low level \type {\halign} command is that it's a lot of work but by providing a set of helpers like here might change that a bit. Keep in mind that much of the above is not new in the sense that we could not achieve the same already, it's just a bit programmer friendly. \stopsectionlevel \startsectionlevel[title=Remark] It can be that the way alignments are interfaced with respect to attributes is a bit different between \LUATEX\ and \LUAMETATEX\ but because the former is frozen (in order not to interfere with current usage patterns) this is something that we will deal with deep down in \CONTEXT\ \LMTX. In principle we can have hooks into the rows for pre and post material but it doesn't really pay of as grouping will still interfere. So for now I decided not to add these. \stopsectionlevel \stopdocument % \hbox \bgroup % \vbox \bgroup \halign \bgroup % \hss\aligncontent\hss\aligntab % \hss\aligncontent\hss\cr % aaaa\aligntab bbbb\cr % aaa\aligntab bbb\cr % aa\aligntab bb\cr % a\aligntab b\cr % \omit\span \hss ccc\hss\cr % \egroup \egroup % \quad % \vbox \bgroup \halign noskips \bgroup % \hss\aligncontent\hss\aligntab % \hss\aligncontent\hss\cr % aaaa\aligntab bbbb\cr % aaa\aligntab bbb\cr % aa\aligntab bb\cr % a\aligntab b\cr % \omit\span \hss ccc\hss\cr % \egroup \egroup % \egroup