The mdwtab^{∗} and mathenv^{†} packages

Mark Wooding

16 May 1996

Contents

1 User guide 2 1.1 The downside . . . . . . . 3 1.2 Syntax . . . . . . . . . . . 5 1.3 An updated \cline com-

mand . . . . . . . . . . . 7 1.4 Spacing control . . . . . . 8 1.5 Creating beautiful long

tables . . . . . . . . . . . 9 1.6 Rules and vertical posi-

tioning . . . . . . . . . . . 11 1.7 User serviceable parts . . 11 1.8 Defining column types . . 11 1.9 Defining new table-

generating environments . 14 1.9.1 Reading preambles 14 1.9.2 Starting new lines 15

1.10 The mathenv package alignment environments . 16 1.10.1 The new eqnarray

environment …. 16 1.10.2 The eqnalign en-

vironment . . . . . 19 1.10.3 A note on spacing

in alignment environments …. 20

1.10.4 Configuring the alignment environments . . . . . . 21

1.11 Other multiline equations 21 1.12 Matrices . . . . . . . . . . 21 1.13 Other mathenv environ-

ments . . . . . . . . . . . 25 2 Implementation of table

handling 26 2.1 Registers, switches and

things . . . . . . . . . . . 26 2.2 Some little details …. 27 2.3 Parser states . . . . . . . 27 2.4 Adding things to token lists 28 2.5 Committing a column to

the preamble . . . . . . . 28 2.6 Playing with parser states 28 2.7 Declaring token types . . 30 2.8 The colset stack . . . . . . 32 2.9 The main parser routine . 32 2.10 Standard column types . . 35 2.11 Paragraph handling … 37 2.12 Gentle persuasion . . . . . 38 2.13 Debugging . . . . . . . . . 38 2.14 The tabular and array en-

vironments . . . . . . . . 38 2.14.1 The environment

routines . . . . . . 39 2.14.2 Setting the strut

height . . . . . . . 40 2.14.3 Setting up the

alignment . . . . . 40 2.14.4 Positioning the

table . . . . . . . . 42 2.14.5 Handling tops

and bottoms … 44

2.15 Breaking tables into bits . 45 2.16 The wonderful world of

    \multicolumn . . . . . . . 46

∗The mdwtab package is currently at version 1.05, dated 16 May 1996.

†The mathenv package is currently at version 1.03, dated 9 May 1996.

2.17 Interlude: range lists … 46 2.18 Horizontal rules OK … 48

2.18.1 Drawing hori-

zontal rules …. 48 2.18.2 Vertical rules … 49 2.18.3 Drawing bits of

lines . . . . . . . . 49 2.18.4 Drawing short

table rows . . . . . 51 2.18.5 Prettifying syntax 52

2.19 Starting new table rows . 54 2.20 Gratuitous grotesquery . . 56 2.21 Error messages . . . . . . 56

3 Implementation of mathenv 58 3.1 Options handling . . . . . 58 3.2 Some useful registers … 58 3.3 A little display handling . 59 3.4 The eqnarray environment 60

3.4.1 The main envir-

onments . . . . . . 60 3.4.2 Newline codes … 63 3.4.3 Setting equation

numbers . . . . . . 63 3.4.4 Numbering control 64 3.4.5 The eqnalign en-

vironment . . . . . 64

3.5 Simple multiline equations 65 3.6 Split equations . . . . . . 65 3.7 Matrix handling . . . . . 68 3.8 Dots. . . . . . . . . . . . . 72 3.9 Lucky dip . . . . . . . . . 73 3.10 Error messages . . . . . . 73

A The GNU General Public Licence 74 A.1 Preamble . . . . . . . . . 74 A.2 Terms and conditions for copying, distribution and modification . . . . . . . . 75

A.3 Appendix: How to Apply These Terms to Your New Programs . . . . . . 79

List of Tables

1 array and tabular column types and modifiers …. 7

2 Parameters for configuring table environments . . 12 3 eqnarray column types

and modifiers . . . . . . . 17 4 Parameters for the eqnarray and eqnalign environments . . . . . . . . . . . 22

1 User guide

The mdwtab package contains a reimplementation of the standard L^{A}TEX tabular and array environments. This is not just an upgraded version: it’s a complete rewrite. It has several advantages over the official array package (not raw L^{A}TEX’s, which is even less nice), and it’s more-or-less compatible. Most of these are rather technical, I’ll admit.

• The newcolumn system is properly and perfectly integrated into the system. There are now no ‘primitive’ column types – all the standard types are created as user-defined columns.

• You can define entirely different table-like environments using the equipment here. It’s still hard work, although less so than before. I’ll do an example of this some time.

• Construction of the preamble is generally much tidier. I’ve used token registers rather than \edef, and it’s all done very nicely.

• Fine spacing before and after rules (described by DEK as ‘a mark of quality’) is now utterly trivial, since the preamble-generator will store the appropriate information.

• You can use array in LR and paragraph modes without having to surround it with ‘$’ signs.

• Usually you don’t want tables in the middle of paragraphs. For these cases, I’ve provided a simpler way to position the table horizontally.

• Footnotes work properly inside tabular environments (hoorah!). You can ‘catch’ footnotes using the minipage environment if you like. (It uses an internal version of the footnote package to handle footnotes, which doesn’t provide extra goodies like the footnote environment; you’ll need to load the full package explicitly to get them.)

• Standard L^{A}TEX tabular environments have a problem with lining up ruled tables. The \firsthline command given in the L^{A}TEX Companion helps a bit, but it’s not really good enough, and besides, it doesn’t actually line the text up right after all. The mdwtab package does the job properly to begin with, so you don’t need to worry.

I’ve tested the following packages with mdwtab, and they all work. Some of the contortions required to make them work weren’t pleasant, but you don’t need to know about them. By a strange coincidence, all the packages were written by David Carlisle. Anyway, here’s the list:

• The quite nice dcolumn package.

• The more useful delarray package.

• The rather spiffy hhline package.

• The truly wonderful tabularx package.

• The utterly magnificent longtable package.

Note that I’ve looked at supertabular as well: it won’t work, so use longtable instead, ’cos it’s much better.

1.1 The downside

There’s no such thing as a free lunch. The mdwtab environment is not 100% compatible with the tabular environment found in L^{A}TEX 2ε or the array package.

The differences between mdwtab and L^{A}TEX 2ε’s tabular environment are as follows:

• The vertical spacing in array environments is different to that in tabular environments. This produces more attractive results in most mathematical uses of arrays, in the author’s opinion. The spacing can be modified by playing with length parameters.

• The presence of horizontal and vertical rules will alter the spacing of the table (so a pair of columns separated by a ‘|’ is wider than a pair with no separation by \arrayrulewidth. This does mean that horizontal and vertical rules match up properly – the usual L^{A}TEX environment makes the horizontal rules stop just short of the edge of the table, making an ugly mess (check out the L^{A}TEX book if you don’t believe me – page 62 provides a good example). The array package handles rules in the same way as mdwtab.

• In common with the array package, there are some restrictions on the use of the \extracolsep command in preambles: you may use at most one \extracolsep command in each ‘@’ or ‘!’ expression. Also, you can’t say

   \newcommand{\xcs}{\extracolsep{\fill}}

and then expect something like ‘...@{\xcs}...’ to actually work – the \extracolsep mustn’t be hidden inside any other commands. Because things like ‘@’ expressions aren’t expanded at the time, \extracolsep has to be searched and processed ‘by hand’.^{1}

• Control sequences (commands) in a table’s preamble aren’t expanded before the preamble is read. In fact, commands in the preamble are considered to be column types, and their names are entirely independent of normal L^{A}TEX commands. No column types of this nature have yet been defined^{2} but the possibility’s always there. Use the \newcolumntype or \coldef commands to define new column types.

• The preamble parsing works in a completely different way. There is a certain amount of compatibility provided, although it’s heavily geared towards keeping longtable happy and probably won’t work with other packages.

• Obscure constructs which were allowed by the old preamble parser but violate the syntax shown in the next section (e.g., ‘|@{}|’ to suppress the \doublerulesep space between two vertical rules, described in The L^{A}TEX Companion as ‘a misuse of the ‘@{...}’ qualifier’) are now properly outlawed. You will be given an error message if you attempt to use such a construction.

• The ‘*’ forms (which repeat column types) are now expanded at a different time. Previously, preambles like ‘c@*{4}{{:}@}{--}c’ were considered valid (the example would expand to ‘c@{:}@{:}@{:}@{:}@{--}c’), because ‘*’s were expanded before the preamble was actually parsed. In the new system, ‘*’ is treated just like any other preamble character (it just has a rather odd action), and preambles like this will result in an error (and probably a rather confusing one).

There are also several incompatibilities between mdwtab and array:

• Because of the way \newcolumntype works in the array package, a horrid construction like

1All \extracolsep does is modify the \tabskip glue, so if you were an evil TEX hacker like me, you could just say ‘\def\xcs{\tabskip=\fill}’ and put ‘...@{\span\xcs}...’ in your preamble.

That’d work nicely. It also works with the array package.

2There used to be an internal \@magic type used by eqnarray, but you’re not supposed to know about that. Besides, it’s not there any more.

   \newcolumntype{x}{{:}}
   \begin{tabular}{|c!xc|}

is considered to be valid, and is interpreted as ‘|c!{:}c|’. My reading of pages 54 and 55 of the L^{A}TEX book tells me that this sort of thing is forbidden in normal L^{A}TEX commands. The mdwtab preamble parser now treats column type letters much more like commands with the result that the hacking above won’t work any more. The construction above would actually be interpreted as ‘|c!{x}c|’ (i.e., the ‘x’ column type wouldn’t be expanded to ‘{:}’ because the parser noticed that it was the argument to the ‘!’ modifier^{3}).

• Most of the points above, particularly those relating to the handling of the preamble, also apply to the array package. it’s not such an advance over the L^{A}TEX 2ε version as everyone said it was.

1.2 Syntax

tabular So that everyone knows where I stand, here’s a complete syntax for my version of

tabular*
array

the tabular environment, and friends

⟨tabular-env⟩ ::=

                   \begin

                                           {tabular}
                             {tabular*}  { ⟨length⟩ }
                             {array}  {smarray}

[ ⟨position-arg⟩ ]

{ ⟨preamble⟩ } ⟨text⟩ \end

                 {tabular}
             {tabular*}
             {array}
             {smarray}

[figure]

⟨position-arg⟩ ::= (see below)

⟨preamble⟩ ::=

⟨first-column⟩

[figure] [figure: ⟨column⟩]

[figure]

⟨first-column⟩ ::= ⟨rule⟩

⟨column⟩ [figure]

⟨column⟩ ::=

⟨spacing⟩

⟨user-pre-text⟩

⟨column-type⟩

⟨spacing⟩ ⟨rule⟩

⟨user-post-text⟩

[figure]

⟨spacing⟩ ::=

@ { ⟨text⟩ }

[figure]

⟨user-pre-text⟩ ::=

[figure]

> { ⟨text⟩ } 3This is a direct result of the way TEX treats undelimited arguments. See chapters 5 and 20 of The TEXbook for more information about how grouping affects argument reading.

⟨column-type⟩ ::=

                                   [figure: T; M; l; c; r]

                   p
                               m
                               b

{ ⟨length⟩ }

# { ⟨raw-pre-text⟩ } { ⟨raw-post-text⟩ }

[figure]

⟨user-post-text⟩ ::=

< { ⟨text⟩ }

[figure]

⟨rule⟩ ::=

| [figure: !; {; ⟨text⟩; }]

[figure]

If you examine the above very carefully, you’ll notice a slight deviation from the original – an @-expression following a rule is considered to be part of the next column, not the current one. This is, I think, an almost insignificant change, and essential for some of the new features. You’ll also notice the new # column type form, which allows you to define new real column types instead of just modifying existing ones. It’s not intended for direct use in preambles – it’s there mainly for the benefit of people who know what they’re doing and insist on using

\newcolumntype anyway.

The actual column types are shown in table 1.

Now that’s sorted everything out, there shouldn’t be any arguments at all about what a column means.

The lowercase ⟨position-arg⟩s ‘t’, ‘c’ and ‘b’ do exactly what they did before: control the vertical positioning of the table. The uppercase ones control the horizontal positioning – this is how you create unboxed tables. You can only create unboxed tables in paragraph mode.

Note that unboxed tables still can’t be broken across pages. Use the longtable package for this, because it already does an excellent job.

\tabpause One thing you can to with unboxed tables, however, is to ‘interrupt’ them, do some normal typesetting, and then continue. This is achieved by the \tabpause command: its argument is written out in paragraph mode, and the table is continued after the argument finishes. Note that it isn’t a real argument as far as commands like \verb are concerned – they’ll work inside \tabpause without any problems.

\vline The \vline command draws a vertical rule the height of the current table cell (unless the current cell is being typeset in paragraph mode – it only works in the simple LR-mode table cells, or in ‘@’ or ‘!’ modifiers). It’s now been given an optional argument which gives the width of the rule to draw:

An example of \vline

A B C D E F

                                       \large

\begin{tabular}

{| c !{\vline[2pt]} c | c |} \hlx{hv} \bf A & \it B & \sf C \\ \hlx{vhv} \bf D & \it E & \sf F \\ \hlx{vh} \end{tabular}

Column types

Name  Meaning

l

Left aligned text (tabular) or equation (array).

c

Centred text (tabular) or equation (array).

r

Right aligned text (tabular) or equation (array).

Ml, Mc and Mr Left, centre and right aligned equations.* Tl, Tc and Tr Left, centre and right aligned text.*

p{⟨width⟩}

Top aligned paragraph with the given width.

m{⟨width⟩}

Vertically centred paragraph with the given width.

b{⟨width⟩}

Bottom aligned paragraph with the given width.

#{⟨pre⟩}{⟨post⟩}

User defined column type: ⟨pre⟩ is inserted before the cell entry, ⟨post⟩ is inserted afterwards.*

Other modifier characters

Name  Meaning

|

Inserts a vertical rule between columns.

!{⟨text⟩}

Inserts ⟨text⟩ between columns, treating it as a vertical rule.

@{⟨text⟩}

Inserts ⟨text⟩ instead of the usual intercolumn space.

>{⟨text⟩}

Inserts ⟨text⟩ just before the actual column entry.

<{⟨text⟩}

Inserts ⟨text⟩ just after the actual column entry.

*{⟨count⟩}{⟨chars⟩}

Inserts ⟨count⟩ copies of the ⟨chars⟩ into the preamble.

* This column type is a new feature

Table 1: array and tabular column types and modifiers

smarray You’ve probably noticed that there’s an unfamiliar environment mentioned in the syntax shown above. The smarray environment produces a ‘small’ array, with script size cells rather than the normal full text size cells. I’ve seen examples of this sort of construction^{4} being implemented by totally unsuitable commands. Someone may find it handy.

1.3 An updated \cline command

\cline The standard L^{A}TEX \cline command has been updated. As well as just passing a range of columns to draw lines through, you can now pass a comma separated list of column numbers and ranges:

4There’s a nasty use of smallmatrix in the testmath.tex file which comes with the amslatex distribution. It’s actually there to simulate a ‘smallcases’ environment, which the mathenv package includes, based around smarray.

⟨cline-cmd⟩ ::=

                    \cline  {

                            ,

⟨number⟩

- ⟨number⟩

                                                                }

[figure]

The positioning of the horizontal lines has also been improved a bit, so that they meet up with the vertical lines properly. Displays like the one in the example below don’t look good unless this has been done properly.

A \cline example

one two three four
five six seven eight

    \newcommand{\mc}{\multicolumn{1}}
    \begin{tabular}[C]{|c|c|c|c|}            \cline{2,4}
      \mc{c|}{one} & two & three & four   \\ \hline
      five & six & seven & \mc{c}{eight}  \\ \cline{1,3}
    \end{tabular}

1.4 Spacing control

One of the most irritating things about L^{A}TEX’s tables is that there isn’t enough space around horizontal rules. Donald Knuth, in The TEXbook, describes addition of some extra vertical space here as ‘a mark of quality’, and since TEX was designed to produce ‘beautiful documents’ it seems a shame that L^{A}TEX doesn’t allow this to be done nicely. Well, it does now.

\vgap The extra vertical space is added using a command \vgap, with the following syntax:

⟨vgap-cmd⟩ ::=

                    \vgap

[ ⟨which-cols⟩ ]

{ ⟨length⟩ } [figure]

⟨which-cols⟩ ::=

                  ,

⟨number⟩

- ⟨number⟩

[figure]

This command must appear either immediately after the beginning of the table or immediately after the \\ which ends a row. (Actually, there are other commands which also have this requirement – you can specify a collection of them wherever you’re allowed to give any one.) It adds some vertical space (the amount is given by the ⟨length⟩) to the table, making sure that the vertical rules of the table are extended correctly.

The \vgap command relies on information stored while your table preamble is being examined. However, it’s possible that you might not want some of the rules drawn (e.g., if you’ve used \multicolumn). The optional ⟨which-cols⟩ argument allows you to specify which rules are not to be drawn. You can specify either single column numbers or ranges. The rule at the very left hand side is given the number 0; the rules at the end of column n are numbered n. It’s easy really.

\hlx Using \vgap is all very well, but it’s a bit cumbersome, and takes up a lot of typing, especially when combined with \hline commands. The \hlx command tries to tidy things.

The syntax is simple:

⟨hlx-cmd⟩ ::=

                    \hlx  {

                                             h

v[⟨which-cols⟩][⟨length⟩]

s[⟨length⟩]

c{⟨which-cols⟩}

                            b

/[⟨number⟩]

                            .

                                                               }

[figure]

The argument works a bit like a table preamble, really. Each letter is a command. The following are supported:

h Works just like \hline. If you put two adjacent to each other, a gap will be put between them.

v[⟨which-cols⟩][⟨length⟩] Works like \vgap[⟨which-cols⟩]{⟨length⟩}. If the ⟨length⟩ is omitted, the value of \doublerulesep is used. This usually looks right.

s[⟨length⟩] Leaves a vertical gap with the given size. If you omit the ⟨length⟩ then \doublerulesep is used. This is usually right.

c{⟨which-cols⟩} Works just like \cline.

b Inserts a backspace the width of a rule. This is useful when doing longtables.

/[⟨number⟩] Allows a page break in a table. Don’t use this except in a longtable environment. The ⟨number⟩ works exactly the same as it does in the \pagebreak command, except that the default is 0, which just permits a break without forcing it.

. (That’s a dot) Starts the next row of the table. No more characters may follow the dot, and no \hline, \hlx, \vgap or \multicolumn commands may be used after it. You don’t have to include it, and most of the time it’s totally useless. It can be handy for some macros, though. I used it in (and in fact added it especially for) the table of column types.

An example of the use of \hlx is given, so you can see what’s going on.

1.5 Creating beautiful long tables

You can use the \vgap and \hlx commands with David Carlisle’s stunning longtable package. However, there are some things you should be away of to ensure that your tables always come out looking lovely.

The longtable package will break a table at an \hline command, leaving a rule at the bottom of the page and another at the top of the next page. This means that a constructions like \hlx{vhv} will be broken into something like \hlx{vh} at the bottom of the page and \hlx{hv} at the top of the next. You need to design the table headers and footers with this in mind.

However, there appears to be a slight problem:^{5} if the footer starts with an Beautiful table example

5You might very well call it a bug. I couldn’t possibly comment.

AT&T Common Stock

Year  Price Dividend
1971  41–54  $2.60
2  41–54  2.70
3  46–55  2.87
4  40–53  3.24
5  45–52  3.40
6  51–59  .95*

* (first quarter only)

    \newcommand{\zerowidth}[1]{\hbox to 0pt{\hss#1\hss}}
    \setlength{\tabcolsep}{1.5em}
    \begin{tabular}[C]{| r | c | r |}                  \hlx{hv[1,2]}
      \multicolumn{3}{|c|}{\bf AT\&T Common Stock}  \\ \hlx{v[1,2]hv}
      \multicolumn{1}{|c|}{\zerowidth{\bf Year}} &
      \multicolumn{1}{c|}{\zerowidth{\bf Price}} &
      \multicolumn{1}{c|}{\zerowidth{\bf Dividend}} \\ \hlx{vhv}
      1971 & 41--54 & \$2.60                        \\
         2 & 41--54 &   2.70                        \\
         3 & 46--55 &   2.87                        \\
         4 & 40--53 &   3.24                        \\
         5 & 45--52 &   3.40                        \\
         6 & 51--59 &    .95\rlap{*}                \\ \hlx{vhs}
      \multicolumn{3}{@{}l}{* (first quarter only)}
    \end{tabular}

\hline, and a page is broken at an \hline, then you get an extra thick rule at the bottom of the page. This is a bit of a problem, because if the rule isn’t there in the footer and you get a break between two rows without a rule between them, then the page looks very odd.

If you want to do ruled longtables, I’d recommend that you proceed as follows:

• End header sections with an \hlx{vh}.

• Begin footer sections with an \hlx{bh}.

• Begin the main table with \hlx{v}.

• Insert \hlx{vhv} commands in the main table body as usual.

If longtable gets modified appropriately, the use of the ‘b’ command won’t be necessary.

Here’s an example of the sort of thing you’d type.

   \begin{longtable}[c]{|c|l|}           \hlx{hv}
   \bf Heading & \bf Also heading     \\ \hlx{vh}
   \endhead
   \hlx{bh}
   \endfoot
   \hlx{v}
   First main & table line            \\ \hlx{vhv}
   Lots of text & like this           \\ \hlx{vhv}

⋮

   Lots of text & like this           \\ \hlx{vhv}
   Last main & table line             \\ \hlx{vh}
   \end{longtable}

1.6 Rules and vertical positioning

In the L^{A}TEX 2ε and array.sty versions of tabular, you run into problems if you try to use ruled tables together with the ‘[t]’ or ‘[b]’ position specifiers – the top or bottom rule ends up being nicely lined up with the text baseline, giving you an effect which is nothing like the one you expected. The L^{A}TEX Companion gives two commands \firsthline and \lasthline which are supposed to help with this problem. (These commands have since migrated into the array package.) Unfortunately, \firsthline doesn’t do its job properly – it gets the text position wrong by exactly the width of the table rules.

The mdwtab package makes all of this automatic. It gets the baseline positions exactly right, whether or not you use rules. Earlier versions of this package required that you play with a length parameter called \rulefudge; this is no longer necessary (or even possible – the length parameter no longer exists). The package now correctly compensates for all sorts of rules and \vgaps at the top and bottom of a table and it gets the positioning right all by itself. You’ve never had it so good.

1.7 User serviceable parts

There are a lot of parameters which you can modify in order to make arrays and tables look nicer. They are all listed in table 2.

1.8 Defining column types

\newcolumntype The easy way to define new column types is using \newcolumntype. It works in more or less the same way as \newcommand:

⟨new-col-type-cmd⟩ ::=

                            \newcolumntype     {  ⟨column-name⟩  }

[ ⟨num-args⟩ ]

[ ⟨default-arg⟩ ]

                                                                       {

⟨first-column⟩

[figure] [figure: ⟨column⟩]

                                                     }

[figure]

(The array.sty implementation doesn’t accept the ⟨default-arg⟩ argument. I’ve no idea why not, ’cos it was very easy to implement.)

\colset This implementation allows you to define lots of different sets of columns. You

Parameter  Meaning

\tabstyle

A command executed at the beginning of a tabular or tabular∗ environment. By default does nothing.

Change using \renewcommand.

\extrarowheight

A length added to the height of every row, used to stop table rules overprinting ascenders. Default 0 pt. Usage is deprecated now: use \hlx instead.

\tabextrasep

Extra space added between rows in a tabular or tabular∗ environment (added before any following \hline). Default 0 pt.

\arrayextrasep

Analogous to \tabextrasep, but for array environments. Default 1 jot (3 pt).

\smarrayextrasep

Analogous to \tabextrasep, but for smarray environments. Default 1 pt.

\tabcolsep

Space added by default on each side of a table cell (unless suppressed by an ‘@’-expression) in tabular environments. Default is defined by your document class.

\arraycolsep

Analogous to \tabcolsep, but for array environments. Default is defined by your document class.

\smarraycolsep

Analogous to \tabcolsep, but for smarray environments. Default is 3 pt.

\arrayrulewidth The width of horizontal and vertical rules in tables.

\doublerulesep

Space added between two adjacent vertical or horizontal rules. Also used by \hlx{v}.

\arraystretch

Command containing a factor to multiply the default row height. Default is defined by your document class (usually 1).

Table 2: Parameters for configuring table environments

can change the current set using the \colset declaration:

⟨colset-cmd⟩ ::=

\colset { ⟨set-name⟩ }

[figure]

This leaves a problem, though: at any particular moment, the current column set could be anything, since other macros and packages can change it.

\colpush What actually happens is that a stack of column sets is maintained. The \colpop \colset command just replaces the item at the top of the stack. The command

\colpush pushes its argument onto the top of the stack, making it the new current set. The corresponding \colpop macro (which doesn’t take any arguments) removes the top item from the stack, reinstating the previous current column set.

⟨colpush-cmd⟩ ::=

\colpush  { ⟨set-name⟩ }

[figure]

⟨colpop-cmd⟩ ::=

\colpop

[figure]

The macros which manipulate the column set stack work locally. The contents of the stack are saved when you open a new group.

To make sure everyone behaves themselves properly, these are the rules for using the column set stack:

• Packages defining column types must ensure that they preserve the current column set. Either they must push their own column type and pop it off when they’re finished defining columns, or they must avoid changing the stack at all, and use the optional arguments to \coldef and \collet.

• Packages must not assume that any particular column set is current unless they have made sure of it themselves.

• Packages must ensure that they pop exactly as much as they push. There isn’t much policing of this (perhaps there should be more), so authors are encouraged to behave responsibly.

• Packages must change the current column set (using \colset) when they start up their table environment. This will be restored when the environment closes.

\coldef \newcolumntype is probably enough for most purposes. However, Real TEXnicians, and people writing new table-generating environments, require something lower-level.

⟨coldef-cmd⟩ ::=

                      \coldef

[ ⟨set-name⟩ ]

⟨col-name⟩

⟨arg-template⟩ { ⟨replacement-text⟩ }

[figure]

Note that this defines a column type in the current colset. It works almost exactly the same way as TEX’s primitive \def. There is a potential gotcha here: a \tab@mkpream token is inserted at the end of your replacement text. If you need to read an optional argument or something, you’ll need to gobble this token before you carry on. The \@firstoftwo macro could be handy here:

   \coldef x{\@firstoftwo{\@ifnextchar[\@xcolumn@i\@xcolumn@ii}}}

This isn’t a terribly pretty state of affairs, and I ought to do something about it. I’ve not seen any use for an optional argument yet, though. Note that if you do gobble the \tab@mkpream, it’s your responsibility to insert another one at the very end of your macro’s expansion (so that further preamble characters can be read).

The replacement text is inserted directly. It’s normal to insert preamble elements here. There are several to choose from:

Column items provide the main ‘meat’ of a column. You insert a column element by saying \tabcoltype{⟨pre-text⟩}{⟨post-text⟩}. The user’s text gets inserted between these two. (So do user pre- and post-texts. Bear this in mind.)

User pre-text items work like the ‘>’ preamble command. You use the \tabuserpretype{⟨text⟩} command to insert it. User pre-texts are written in reverse order between the pre-text of the column item and the text from the table cell.

User post-text items work like the ‘<’ preamble command. You use the \tabuserposttype{⟨text⟩} command to insert it. Like user pre-texts, user post-texts are written in reverse order, between the table cell text and the column item post-text.

Space items work like the ‘@’ preamble command. They’re inserted with the \tabspctype{⟨text⟩} command.

Rule items work like the ‘|’ and ‘!’ commands. You insert them with the \tabruletype{⟨text⟩} command. Note that the text is inserted by \vgap too, so it should contain things which adjust their vertical size nicely. If you really need to, you can test \iftab@vgap to see if you’re in a \vgap.

\collet As well as defining columns, you can copy definitions (rather like \let allows you to copy macros). The syntax is like this:

⟨collet-cmd⟩ ::= [ ⟨set-name⟩ ] =

⟨col-name⟩

[ ⟨set-name⟩ ]

⟨col-name⟩ [figure]

(In other words, you can copy defintions from other column sets.)

1.9 Defining new table-generating environments

Quite a few routines are provided specifically to help you to define new environments which do alignment in a nice way.

1.9.1 Reading preambles

The main tricky bit in doing table-like environments is parsing preambles. No longer.

\tab@readpreamble The main parser routine is called \tab@doreadpream. Given a user preamble \tab@doreadpream string as an argument, it will build an \halign preamble to return to you.

However, the preamble produced won’t be complete. This is because you can actually make multiple calls to \tab@doreadpream with bits of user preambles. The \newcolumntype system uses this mechanism, as does the ‘*’ (repeating) modifier. When there really is no more preamble to read, you need to commit the heldover tokens to the output. The \tab@readpreamble routine will do this for you – given a user preamble, it builds a complete output from it.

A token register \tab@preamble is used to store the generated preamble. Before starting, you must iniitialise this token list to whatever you want. There’s another token register, \tab@shortline, which is used to store tokens used by \vgap. For each column in the table, the list contains an \omit (to override the standard preamble) and an \hfil space taking up most of the column. Finally, for each rule item in the user preamble, the shortline list contains an entry of the form:

\tab@ckr{⟨column-number⟩}{⟨rule-text⟩}

This is used to decide whether to print the rule or an empty thing of the same width. You probably ought to know that the very first column does not have a leading \omit – this is supplied by \vgap so that it can then look for optional arguments.

\tab@initread  As well as initialising \tab@preamble and emptying \tab@shortline, there

are several other operations required to initialise a preamble read. These are all performed by the \tab@initread macro, although you may want to change some of the values for your specific application. For reference, the actions performed are:

• initialising the parser state by setting \tab@state = \tab@startstate;

   • clearing the token lists \tab@preamble and \tab@shortlist;
   • initialising the macros \tab@tabtext, \tab@midtext, and \tab@multicol

to their default values of ‘&’, ‘\ignorespaces#\unskip’ and the empty token list respectively.^{6}

• clearing the internal token list registers \tab@pretext, tab@userpretext

     and \tab@posttext;

• clearing the column counter \tab@columns to zero;

• clearing the action performed when a new column is started (by making the

     \tab@looped macro equal to \relax; this is used to make \multicolumn

macro raise an error if you try to do more than one column); and

• setting up some other switches used by the parser (\iftab@rule,

     \iftab@initrule and \iftab@firstcol, all of which are set to be true).

The macro \tab@multicol is used by the \multicolumn command to insert any necessary items (e.g., struts) before the actual column text. If you set this to something non-empty, you should probably consider adding a call to the macro to the beginning of \tab@preamble.

When parsing is finally done, the count register \tab@columns contains the number of columns in the alignment. Don’t corrupt this value, because it’s used for handling \hline commands.

1.9.2 Starting new lines

The other messy bit required by table environments is the newline command \\. There are nasty complications involved with starting new lines, some of which can be handled by this package, and some on which I can only give advice.

\tab@cr The optional arguments and star-forms etc. can be read fairly painlessly using the \tab@cr command:

⟨tabcr-cmd⟩ ::=

\tab@cr ⟨command⟩ { ⟨non-star-text⟩ } {

⟨star-text⟩ }

This will call your ⟨command⟩ with two arguments. The first is the contents of the optional argument, or ‘\z@’ if there wasn’t one. The second is either ⟨star-text⟩ or ⟨non-star-text⟩ depending on whether the user wrote the ∗-form or not.

6These are macros rather than token lists to avoid hogging all the token list registers. Actually, the package only allocates two, although it does use almost all of the temporary registers as well.

Also, there’s a lie: \unskip is too hamfisted to remove trailing spaces properly; I really use a macro called \@maybe@unskip

Somewhere in your ⟨command⟩, you’ll have to use the \cr primitive to end the table row. After you’ve done this, you must ensure that you don’t do anything that gets past TEX’s mouth without protecting it – otherwise \hline and co. won’t work. I usually wrap things up in a \noalign to protect them, although there are other methods. Maybe.

You might like to have a look at the eqnarray implementation provided to see how all this gets put into practice.

1.10 The mathenv package alignment environments

The mathenv package provides several environments for aligning equations in various ways. They’re mainly provided as a demonstration of the table handling macros in mdwtab, so don’t expect great things. If you want truly beautiful mathematics, use amsmath.^{7} However, the various environments do nest in an approximately useful way. I also think that the matrix and script environments provided here give better results than their amsmath equivalents, and they are certainly more versatile.

1.10.1 The new eqnarray environment

eqnarray As an example of the new column defining features, and because the original eqnarray* isn’t terribly good, I’ve included a rewritten version of the eqnarray environment.

The new implementation closes the gap between eqnarray and AMS-TEX alignment features. It’s in a separate, package called mathenv, to avoid wasting your memory.

⟨eqnarray-env⟩ ::=

⟨begin-eqnarray⟩

[figure: \\] [figure: ⟨row⟩]

⟨end-eqnarray⟩

[figure]

⟨begin-eqnarray⟩ ::=

                              \begin

                                                    {eqnarray}
                                            {eqnarray*}

                      [

⟨eqa-column⟩

                                           ]

[figure]

⟨eqa-column⟩ ::=

                      [figure: q] [figure: :]

[figure] [figure: >; {; ⟨pre-text⟩; }]

                  [figure: T; r; c; l]

                 L
                 x

< { ⟨post-text⟩ }

[figure]

⟨end-eqnarray⟩ ::=

                       \end

                                {eqnarray}
                          {eqnarray*}

[figure]

Descriptions of the various column types are given in table 3.

The default preamble, if you don’t supply one of your own, is ‘rcl’. Most of the time, ‘rl’ is sufficient, although compatibility is more important to me.

7Particularly since nice commands like \over are being reactivated in a later release of amsmath.

Column types

Name  Meaning

l

Left aligned piece of equation.

c

Centred piece of equation.

x

Centred or flush-left whole equation (depending on fleqn option).

r

Right aligned piece of equation.

L

Left aligned piece of equation whose width is considered to be 2 em.

Tl, Tc and Tr Left, centre and right aligned text.

Other modifier characters

Name  Meaning

:

Leaves a big gap between equations. By default, the ‘chunks’ separated by ‘:’s are equally spaced on the line.

q

Inserts 1 em of space

>{⟨text⟩}

Inserts ⟨text⟩ just before the actual column entry.

<{⟨text⟩}

Inserts ⟨text⟩ just after the actual column entry.

*{⟨count⟩}{⟨chars⟩}

Inserts ⟨count⟩ copies of the ⟨chars⟩ into the preamble.

Table 3: eqnarray column types and modifiers

By default, there is no space between columns, which makes formulæ in an eqnarray environment look just like formulæ typeset on their own, except that things get aligned in columns. This is where the default eqnarray falls down: it leaves \arraycolsep space between each column making the thing look horrible.

An example would be good here, I think. This one’s from exercise 22.9 of the TEXbook.

Simultaneous equations \begin{aligned}10w & + & 3x & + & 3y & + & 18z & = 1 & (1) \\ 6w & − & 17x & & & − & 5z & = 2 & (2) \end{aligned}

\begin{eqnarray}[*3{rc}rl]
10w & + &  3x & + & 3y & + & 18z & = 1 \\
6w & - & 17x &   &    & - &  5z & = 2
\end{eqnarray}

Choosing a more up-to-date example, here’s some examples from the L^{A}TEX Companion.

Lots of equations \begin{aligned}V_{i} & = v_{i} − q_{i}v_{j}, & X_{i} & = x_{i} − q_{i}x_{j}, & U_{i} = u_{i}, for i ≠ j & (3) \\ V_{j} & = v_{j}, & X_{j} & = x_{j} & U_{j}u_{j} + ∑_{i≠j} q_{i}u_{i}. & (4) \end{aligned}

 \begin{eqnarray}[rl:rl:lq]
  V_i &= v_i - q_i v_j, & X_i &= x_i - q_i x_j, &
        U_i = u_i, \qquad \mbox{for $i \ne j$}  \\
  V_j &= v_j,           & X_j &= x_j            &
        U_j u_j + \sum_{i \ne j} q_i u_i. \label{eq:A}
 \end{eqnarray}

Plain text column and \tabpause

x = y  by (4)  (5)
x′ = y′  by definition  (6)

and

x + x′ = y + y′  by Axiom 1  (7)

 \begin{eqnarray}[rlqqTl]
      x  &= y           & by (\ref{eq:A}) \\
      x' &= y'          & by definition \\
 \tabpause{and}
  x + x' &= y + y'      & by Axiom~1
 \end{eqnarray}

The new features also mean that you don’t need to mess about with \lefteqn any more. This is handled by the ‘L’ column type:

Splitting example

\begin{matrix}w + x + y + z = \\ a + b + c + d + e+ \\ f + g + h + i + j \end{matrix}

Finally, just to prove that the spacing’s right at last, here’s another one from the Companion.

\begin{eqnarray*}[Ll]

w+x+y+z = \\ & a+b+c+d+e+ \\ & f+g+h+i+j \end{eqnarray*}

Spacing demonstration

\begin{aligned}x^{2} + y^{2} = z^{2} & (8) \\ x^{2} + y^{2} & = z^{2} & (9) \\ y^{2} & < z^{2} & (10) \end{aligned} \begin{equation} x^2 + y^2 = z^2

                                       \end{equation}
                                       \begin{eqnarray}[rl]
                                         x^2 + y^2 &= z^2 \\
                                               y^2 &< z^2
                                       \end{eqnarray}

Well, that was easy enough. Now on to numbering. As you’ve noticed, the equations above are numbered. You can use the eqnarray∗ environment to turn off the numbering in the whole environment, or say \nonumber on a line to suppress numbering of that one in particular.

\eqnumber More excitingly, you can say \eqnumber to enable numbering for a particular equation, or \eqnumber[⟨text⟩] to choose what to show instead of the line number. This works for both starred and unstarred versions of the environment. Now \nonumber becomes merely a synonym for ‘\eqnumber[]’.

A note for cheats: you can use the sparkly new eqnarray for simple equations by specifying ‘x’ as the column description. Who needs AMS-TEX? ;-)

eqlines In fact, there’s a separate environment eqlines, which is equivalent to eqnarray eqlines* with a single ‘x’ column; the result is that you can insert a collection of displayed

equations separated by \\ commands. If you don’t like numbering, use eqlines∗ insead.

1.10.2 The eqnalign environment

eqnalign There’s a new environment, eqnalign, which does almost the same thing as eqnarray but not quite. It doesn’t do equation numbers, and it wraps its contents up in a box. The result of this is that:

• You can use eqnalign for just a part of a formula. The eqnarray environment must take up the whole display.

• You can use eqnalign within eqnarray for extra fine alignment of subsidiary bits.

• You can break off from doing an eqnarray using the \tabpause command. You can’t use \tabpause inside eqnalign.^{8}

The eqnalign environment works like this:

⟨eqnalign-env⟩ ::=

⟨begin-eqnalign⟩ ⟨contents⟩ ⟨end-eqnalign⟩

[figure]

⟨begin-eqnalign⟩ ::=

                        \begin   {eqnalign}

                                                                     ]
                 [     t
                      c
                      b

                           ]

[figure]

[

⟨eqa-column⟩ 8Well, technically speaking there’s nothing to stop you. However, the results won’t be pretty.

⟨end-eqnalign⟩ ::=

                      \end  {eqnalign}

[figure]

As the syntax suggests, the preamble for the eqnalign environment works exactly the same way as for eqnarray. Example time: another one from the TEXbook.

Example of eqnalign

\begin{cases}α & = f(z) \\ β & = f(z^{2}) \\ γ & = f(z^{3}) \end{cases}\} \begin{cases}x & = α^{2} − β \\ y & = 2γ \end{cases}\} .

    \[
      \left\{ \begin{eqnalign}[rl]
        \alpha &= f(z) \\ \beta  &= f(z^2) \\
        \gamma &= f(z^3)
      \end{eqnalign} \right\}
      \qquad
      \left\{ \begin{eqnalign}[rl]
        x &= \alpha^2 - \beta \\ y &= 2\gamma
      \end{eqnalign} \right\}.
    \]

\multicolumn The \multicolumn command works correctly in both the eqnarray and eqnalign environments, although you should bear in mind that you should give eqnarray column types, not array ones.

1.10.3 A note on spacing in alignment environments

Most of the time, equations in eqnarray and eqnalign environments will be beautiful. However, there are some things you should bear in mind when you produce beautiful equations.

The main problem with spacing is making sure that binary relations and binary operators have the correct amount of space on each side of them. The alignment environments insert ‘hidden’ objects at the ends of table cells to assist with the spacing: ‘l’ column types have a hidden object on the left, ‘r’ types have a hidden object on the right, and ‘c’ types have a hidden object on both ends. These hidden objects add the correct space when there’s a binary operator or relation next to them. If some other sort of object is lurking there, no space is added. So far, so good.

The only problem comes when you have something like this:

How not to do an eqnarray

\begin{aligned}x + y & = & 12 \\ 2x − 5y & = & − 6 \end{aligned}

\begin{eqnarray*}[rcl]

x +  y & = & 12 \\ 2x - 5y & = & -6 \end{eqnarray*}

The ‘−’ sign in the second equation has been treated as a binary operator when really it should be a unary prefix operator, but TEX isn’t clever enough to know the difference. (Can you see the difference in the spacing between −6 and − 6?) There are two possible solutions to the problem. You could wrap the ‘-6’ up in a group (‘{-6}’), or just the − sign (‘{-}6’). A better plan, though, is to get rid of the middle column altogether:

How to do an eqnarray

\begin{aligned}x + y & = 12 \\ 2x − 5y & = −6 \end{aligned}

                                       \begin{eqnarray*}[rl]
                                         x +  y & = 12 \\
                                        2x - 5y & = -6
                                       \end{eqnarray*}

Since the things in the middle column were the same width, it’s not actually doing any good. Also, now that TEX can see that the thing on the left of the ‘−’ sign is a relation (the ‘=’ sign), it will space the formula correctly.

In this case, it might be even better to add some extra columns, and line up the x and y terms in the left hand side:

Extra beautiful eqnarray

\begin{aligned}x + & y & = 12 \\ 2x − & 5y & = −6 \end{aligned}

                                       \begin{eqnarray*}[rrl]
                                         x + &  y & = 12 \\
                                        2x - & 5y & = -6
                                       \end{eqnarray*}

There’s no need to put the ‘+’ and ‘−’ operators in their own column here, because they’re both 7.7778 pt wide, even though they don’t look it.

1.10.4 Configuring the alignment environments

There are a collection of parameters you can use to make the equation alignment environments (eqnarray and eqnalign) look the way you like them. These are all shown in table 4.

1.11 Other multiline equations

Sometimes there’s no sensible alignment point for splitting equations. The normal thing to do under these circumstances is to put the first line way over to the left of the page, and the last line over to the right. (If there are more lines, I imagine we put them in the middle.)

spliteqn The spliteqn environment allows you to do such splitting of equations. Rather spliteqn* than tediously describe it, I’ll just give an example, because it’s really easy. The

∗-version works the same, except it doesn’t put an equation number in.

subsplit If you have a very badly behaved equation, you might want to split a part of it (say, a bit of a fraction), particularly if you’re doing things in narrow columns.

1.12 Matrices

Also included in the mathenv package is a collection of things for typesetting matrices. The standard array doesn’t (in my opinion) provide the right sort of

Parameter  Use

\eqaopenskip Length put on the left of an eqnarray environment. By default, this is \@centering (to centre the alignment) or \mathindent (to left align) depending on whether you’re using the fleqn document class option.

\eqacloseskip Length put on the right of an eqnarray environment. By default, this is \@centering, to align the environment correctly.

\eqacolskip Space added by the ‘:’ column modifier. This should be a rubber length, although it only stretches in eqnarray, not in eqnalign. The default value is 1^{1}/_{2} em with 1000 pt of stretch.

\eqainskip Space added at each side of a normal column. By default this is 0 pt.

\eqastyle The maths style used in the alignment. By default, this is \textstyle, and you probably won’t want to change it.

Table 4: Parameters for the eqnarray and eqnalign environments

A split equation \begin{aligned}∑_{1≤j≤n} & \frac{1}{(x_{j} − x_{1}) … (x_{j} − x_{j−1})(x − x_{j})(x_{j} − x_{j+1}) … (x_{j} − x_{n})} \\ & = \frac{1}{(x − x_{1}) … (x − x_{n})}. & (11) \end{aligned}

\begin{spliteqn}
\sum_{1\le j\le n}
\frac {1} { (x_j - x_1) \ldots (x_j - x_{j-1})
(x - x_j) (x_j - x_{j+1}) \ldots (x_j - x_n) }
\\
= \frac {1} { (x - x_1) \ldots (x - x_n) }.
\end{spliteqn}

A subsplit environment

\frac{\begin{matrix}q^{\frac{1}{2} n(n+1)}(ea; q^{2})_{∞}(eq/a; q^{2})_{∞} \\ (caq/e; q^{2})_{∞}(cq^{2}/ae; q^{2})_{∞} \end{matrix}}{(e; q)_{∞}(cq/e; q)_{∞}}  (12)

    \begin{equation}
      \frac{
        \begin{subsplit}
          q^{\frac{1}{2} n(n+1)}(ea; q^2)_\infty (eq/a; q^2)_\infty \\
                     (caq/e; q^2)_\infty (cq^2/ae; q^2)_\infty
        \end{subsplit}
      }{
        (e; q)_\infty (cq/e; q)_\infty
      }
    \end{equation}

spacing for matrices. Plain TEX provides some quite nice matrix handling macros, but they don’t work in the appropriate L^{A}TEX way.

Warning: These definitions will make old versions of plain.sty unhappy; newer versions correctly restore the Plain TEX macros \matrix and \pmatrix.

matrix The simple way to do matrices is with the matrix environment.

⟨matrix-env⟩ ::=

⟨begin-matrix⟩ ⟨contents⟩ ⟨end-matrix⟩

[figure: −]

⟨begin-matrix⟩ ::=

                      \begin{matrix}

[ ⟨matrix-cols⟩ ]

[figure]

⟨matrix-cols⟩ ::=

                   [figure] [figure: [; T; l; c; r]

[figure]

⟨end-matrix⟩ ::=

                     \end{stack}

[figure]

The ‘l’, ‘c’ and ‘r’ columns are fairly obvious – they align their contents in the appropriate way. The ‘[’ character is more complicated. It means ‘repeat the remaining column types forever’, so a preamble of ‘cc[lr’ means ‘two centred columns, then alternating left- and right-aligned columns for as often as needed’. The default preamble, if you don’t specify one, is ‘[c’ – ‘any number of centred columns’.

\multicolumn The \multicolumn command works correctly in matrices, although you should

bear in mind that you should give matrix column types, not array ones.

pmatrix The standard matrix environment doesn’t put any delimiters around the matrix. You can use the standard \left and \right commands, although this is a bit nasty. The pmatrix environment will put parentheses around the matrix it creates; it’s otherwise exactly the same as matrix.

dmatrix A dmatrix environment is also provided. It takes two extra arguments: the left

Various matrix environments

\begin{matrix}1 & 0 \\ 0 & −1 \end{matrix} ( \begin{matrix}cos θ & sin θ \\ − sin θ & cos θ \end{matrix} ) [ \begin{matrix}0 & −i \\ i & 0 \end{matrix} ]

    \[ \begin{matrix} 1 & 0 \\ 0 & -1 \end{matrix} \quad
       \begin{pmatrix}
         \cos\theta & \sin\theta \\
        -\sin\theta & \cos\theta
       \end{pmatrix} \quad
       \begin{dmatrix}[] 0 & -i \\ i & 0 \end{dmatrix}
    \]

and right delimiter characters (without \left or \right).

smatrix Normal matrices always come out the same size; they don’t change size according to the surrounding context (unfortunately). However, it can be occasionally useful to put matrices in running text, so you can talk about A being (\begin{matrix}a & b \\ b & c \end{matrix}) being its own transpose (i.e., A = A^{T}). This is accomplished using the smatrix (the ‘s’ stands for ‘small’ – I thought that ‘smallmatrix’ was too big to type inline). As well as inline text, the smatrix can be useful in displays, if the matrix is deep in a subformula. I can’t think of any examples offhand, though.

spmatrix The smatrix environment doesn’t supply any delimiters, like matrix. There are sdmatrix spmatrix and sdmatrix environments which do, though. Note that delimiters have

a tendency to get too big and mess up the line spacing – I had to use explicitly \big delimiters in the above example.

pmatrix* All the small matrix environments have starred versions, which are more suit-

spmatrix*
sdmatrix*

able for use in displays, since they have more space between the rows. They’re intended for typesetting really big matrices in displays.

\ddots The standard \vdots and \ddots commands don’t produce anything at all \vdots nice in small matrices, so this package redefines them so that they scale properly

to smaller sizes.

genmatrix Actually, all these environments are special cases of one: genmatrix. This takes oodles of arguments:

\begin{genmatrix}{⟨matrix-style⟩}{⟨outer-style⟩}
{⟨spacing⟩}{⟨left-delim⟩}{⟨right-delim⟩}

⋮

     \end{genmatrix}

The two ‘style’ arguments should be things like \textstyle or \scriptstyle; the first, ⟨matrix-style⟩, is the style to use for the matrix elements, and the second, ⟨outer-style⟩, is the style to assume for the surrounding text (this affects the spacing within the matrix; it should usually be the same as ⟨matrix-style⟩). The ⟨spacing⟩ is inserted between the matrix and the delimiters, on each side of the matrix. It’s usually ‘\,’ in full-size matrices, and blank for small ones. The delimiters are inserted around the matrices, and sized appropriately. newmatrix You can create your own matrix environments if you like, using the \newmatrix

command. It takes two arguments, although they’re a bit odd. The first is the name of the environment, and the second contains the arguments to pass to genmatrix. For example, the pmatrix environment was defined by saying

   \newmatrix{pmatrix}{{\textstyle}{\textstyle}{\,}{(}{)}}

If you don’t pass all three arguments, then you end up requiring the user to specify the remaining ones. This is how dmatrix works.

script Finally, although it’s not really a matrix, stacked super- and subscripts follow much the same sorts of spacing rules. The script environment allows you to do this sort of thing very easily. It essentially provides a ‘matrix’ with the right sort of spacing. The default preamble string is ‘c’, giving you centred scripts, although you can say \begin{script}[l] for left-aligned scripts, which is better if the script is being placed to the right of its operator. If you’re really odd, you can have more than one column.

Example of script

\begin{matrix}∑ & ′ \\ x∈A \end{matrix} f(x) =^{def} ∑_{\begin{matrix}x∈A \\ x≠0 \end{matrix}} f(x)

                                       \[ \mathop{{\sum}'}_{x \in A}
                                          f(x)
                                          \stackrel{\mathrm{def}}{=}
                                          \sum_{\begin{script}
                                            x \in A \\ x \ne 0
                                          \end{script}} f(x)
                                       \]

1.13 Other mathenv environments

The mathenv package contains some other environments which may be useful, based on the enhanced tabular and array environments.

cases The cases environment lets you say things like the following: Example of cases

P_{r−j} = \{ \begin{matrix}0 & if r − j is odd \\ r! (−1)^{(r−j)/2} & if r − j is even \end{matrix}

    \[ P_{r-j} = \begin{cases}
                   0 & if $r-j$ is odd \\
                   r!\,(-1)^{(r-j)/2} & if $r-j$ is even
                 \end{cases}
    \]

The spacing required for this is a bit messy, so providing an environment for it is quite handy.

smcases The smcases environment works the same way as cases, but with scriptsize lettering.

2 Implementation of table handling

Here we go. It starts horrid and gets worse. However, it does stay nicer than the original, IMHO.

1 ⟨∗mdwtab⟩

2.1 Registers, switches and things

We need lots of these. It’s great fun.

The two count registers are simple enough:

\tab@state contains the current parser state. Since we probably won’t be parsing preambles recursively, this is a global variable.

\tab@columns contains the number of the current column.

\tab@hlstate contains the state required for hline management.

 2 \newcount\tab@state
 3 \newcount\tab@columns

We need lots of token registers. Fortunately, most of them are only used during parsing. We’ll use Plain TEX’s scratch tokens for this. Note that \toks\tw@ isn’t used here. It, and \toks@, are free for use by column commands.

 4 \newtoks\tab@preamble
 5 \newtoks\tab@shortline
 6 \toksdef\tab@pretext 4
 7 \toksdef\tab@posttext 6
 8 \toksdef\tab@userpretext 8

The dimens are fairly straightforward. The inclusion of \col@sep is a sacrifice to compatibility – judicious use of \let in array would have saved a register.

 9 \newdimen\extrarowheight
10 \newdimen\tabextrasep
11 \newdimen\arrayextrasep
12 \newdimen\smarraycolsep
13 \newdimen\smarrayextrasep
14 \newdimen\tab@width
15 \newdimen\col@sep
16 \newdimen\tab@endheight

Some skip registers too. Phew.

17 \newskip\tab@leftskip
18 \newskip\tab@rightskip

And some switches. The first three are for the parser.

19 \newif\iftab@firstcol
20 \newif\iftab@initrule
21 \newif\iftab@rule
22 \newif\iftab@vgap

Now assign some default values to new dimen parameters. These definitions are essentially the equivalent of an \openup 1\jot in array, but not in tabular. This looks nice, I think.

23 \tabextrasep\z@
24 \arrayextrasep\jot
25 \smarraycolsep\thr@@\p@
26 \smarrayextrasep\z@

Set some things up for alien table environments.

27 \let\tab@extrasep\tabextrasep
28 \let\tab@penalty\relax

2.2 Some little details

\@maybe@unskip This macro solves a little problem. In an alignment (and in other places) it’s desirable to suppress trailing space. The usual method, to say \unskip, is a little hamfisted, because it removes perfectly reasonable aligning spaces like \hfils. While as a package writer I can deal with this sort of thing by saying \kern\z@ in appropriate places, it can annoy users who are trying to use \hfill to override alignment in funny places.

My current solution seems to be acceptable. I’ll remove the natural width of the last glue item, so that it can still stretch and shrink if necessary. The implementation makes use of the fact that multiplying a ⟨skip⟩ by a ⟨number⟩ kills off the stretch. (Bug fix: don’t do this when we’re in vertical mode.)

29 \def\@maybe@unskip{\ifhmode\hskip-\@ne\lastskip\relax\fi}

\q@delim Finally, for the sake of niceness, here’s a delimiter token I can use for various things. It’s a ‘quark’, for what it’s worth (i.e., it expands to itself) although I’m not really sure why this is a good thing. As far as I’m concerned, it’s important that it has a unique meaning (i.e., that it won’t be \ifx-equal to other things, or something undefined) and that it won’t be used where I don’t expect it to be used. TEX will loop horridly if it tries to expand this, so I don’t think that quarks are wonderfully clever thing to use. (Maybe it should really expand to something like ‘⟨quark⟩.’, which will rapdly fill TEX’s memory if it gets accidentally expanded. Still, I’ll leave it as it is until such time as I understand the idea more.)

30 \def\q@delim{\q@delim}

2.3 Parser states

Now we start on the parser. It’s really simple, deep down. We progress from state to state, extracing tokens from the preamble and building command names from them. Each command calls one of the element-building routines, which works out which state it should be in. We go through each of the states in between (see later) doing default things for the ones we missed out.

Anyway, here’s some symbolic names for the states. It makes my life easier.

31 \chardef\tab@startstate 0
32 \chardef\tab@loopstate 1
33 \chardef\tab@rulestate 1
34 \chardef\tab@prespcstate 2
35 \chardef\tab@prestate 3

36 \chardef\tab@colstate 4
37 \chardef\tab@poststate 5
38 \chardef\tab@postspcstate 6
39 \chardef\tab@limitstate 7

2.4 Adding things to token lists

Define some macros for adding stuff to the beginning and end of token lists. This is really easy, actually. Here we go.

40 \def\tab@append#1#2{#1\expandafter{\the#1#2}}
41 \def\tab@prepend#1#2{%
42 \toks@{#2}#1\expandafter{\the\expandafter\toks@\the#1}%

43 }

2.5 Committing a column to the preamble

Each time we pass the ‘rule’ state, we ‘commit’ the tokens we’ve gathered so far to the main preamble token list. This is how we do it. Note the icky use of

\expandafter.
44 \def\tab@commit{%

If this isn’t the first column, then we need to put in a column separator.

45 \iftab@firstcol\else%
46  \expandafter\tab@append\expandafter\tab@preamble%
47  \expandafter{\tab@tabtext}%
48 \fi%

Now we spill the token registers into the main list in a funny order (which is why we’re doing it in this strange way in the first place.

49 \toks@\expandafter{\tab@midtext}%
50 \tab@preamble\expandafter{%
51  \the\expandafter\tab@preamble%
52  \the\expandafter\tab@pretext%
53  \the\expandafter\tab@userpretext%
54  \the\expandafter\toks@%
55  \the\tab@posttext%
56 }%

Now reset token lists and things for the next go round.

57 \tab@firstcolfalse%
58 \tab@pretext{}%
59 \tab@userpretext{}%
60 \tab@posttext{}%

61 }

2.6 Playing with parser states

\tab@setstate This is how we set new states. The algorithm is fairly simple, really.

while tab state ≠ s do tab state = tab state + 1; if tab state = tab limitState then tab state = tab loopState; if tab state = tab preSpcState then

if tab initRule then tab initRule = false;

else if tab inMultiCol then moan; commit; append(tab shortLine, ‘&\omit’);

end if; end if; if tab state ≠ s then do default(tab state);

end while;

First we decide if there’s anything to do. If so, we call another macro to do it for us.

62 \def\tab@setstate#1{%
63 \ifnum#1=\tab@state\else%
64  \def\@tempa{\tab@setstate@i{#1}}%
65  \@tempa%
66 \fi%

67 }

This is where the fun is. First we bump the state by one, and loop back if we fall off the end.

68 \def\tab@setstate@i#1{%
69 \global\advance\tab@state\@ne%
70 \ifnum\tab@state>\tab@limitstate%
71  \global\tab@state\tab@loopstate%
72 \fi%

Now, if we’ve just passed the ruleoff state, we commit the current text unless this was the strange initial rule at the very beginning. We provide a little hook here so that \multicolumn can moan if you try and give more than one column there. We also add another tab/omit pair to the list we use for \vgap.

73 \ifnum\tab@state=\tab@prespcstate%
74  \iftab@initrule%
75  \tab@initrulefalse%
76  \else%
77  \tab@looped%
78  \tab@commit%
79  \tab@append\tab@shortline{&\omit}%
80  \fi%
81 \fi%

Now we decide whether to go round again. If not, we do the default thing for this state. This is mainly here so that we can put the \tabcolsep or whatever in if the user didn’t give an ‘@’ expression.

82 \ifnum#1=\tab@state%
83  \let\@tempa\relax%
84 \else%
85  \csname tab@default@\number\tab@state\endcsname%
86 \fi%
87 \@tempa%

88 }

Now we set up the default actions for the various states.

In state 2 (pre-space) we add in the default gap if either we didn’t have an ‘@’ expression in the post-space state or there was an explicit intervening rule.

 89 \@namedef{tab@default@2}{%
 90 \iftab@rule%
 91  \tab@append\tab@pretext{\hskip\col@sep}%
 92 \fi%

93 }

If the user omits the column type, we insert an ‘l’-type column and moan a lot.

 94 \@namedef{tab@default@4}{%
 95 \tab@err@misscol%
 96 \tab@append\tab@pretext{\tab@bgroup\relax}%
 97 \tab@append\tab@posttext{\relax\tab@egroup\hfil}%
 98 \tab@append\tab@shortline{\hfil}%
 99 \advance\tab@columns\@ne%

100 }

Finally we deal with the post-space state. We set a marker so that we put in the default space in the pre-space state later too.

101 \@namedef{tab@default@6}{%
102 \tab@append\tab@posttext{\hskip\col@sep}%
103 \tab@ruletrue%

104 }

2.7 Declaring token types

\tab@extracol Before we start, we need to handle \extracolsep. This is a right pain, because the original version of tabular worked on total expansion, which is a Bad Thing. On the other hand, turning \extracolsep into a \tabskip is also a major pain.

105 \def\tab@extracol#1#2{\tab@extracol@i#1#2\extracolsep{}\extracolsep\end}
106 \def\tab@extracol@i#1#2\extracolsep#3#4\extracolsep#5\end{%
107 \ifx @#3@%
108  \def\@tempa{#1{#2}}%
109 \else%
110  \def\@tempa{#1{#2\tabskip#3\relax#4}}%
111 \fi%
112 \@tempa%

113 }

This is where we do the work for inserting preamble elements.

\tabruletype Inserting rules is interesting, because we have to decide where to put them. If this is the funny initial rule, it goes in the pre-text list, otherwise it goes in the post-text list. We work out what to do first thing:

114 \def\tabruletype#1{\tab@extracol\tabruletype@i{#1}}%
115 \def\tabruletype@i#1{%
116 \iftab@initrule%
117  \let\tab@tok\tab@pretext%
118 \else%
119  \let\tab@tok\tab@posttext%
120 \fi%

Now if we’re already in the rule state, we must have just done a rule. This means we must put in the \doublerulesep space, both here and in the shortline list. Otherwise we just stick the rule in.

This is complicated, because \vgap needs to be able to remove some bits of rule. We pass each one to a macro \tab@ckr, together with the column number, which is carefully bumped at the right times, and this macro will vet the rules and output the appropriate ones. There’s lots of extreme \expandafter nastiness as a result. Amazingly, this actually works.

121 \ifnum\tab@state=\tab@rulestate%
122  \tab@append\tab@tok{\hskip\doublerulesep\begingroup#1\endgroup}%
123  \expandafter\tab@append\expandafter\tab@shortline\expandafter{%
124  \expandafter\hskip\expandafter\doublerulesep%
125  \expandafter\tab@ckr\expandafter{\the\tab@columns}%
126  {\begingroup#1\endgroup}%

127 }%

128 \else%
129  \tab@setstate\tab@rulestate%
130  \tab@append\tab@tok{\begingroup#1\endgroup}%
131  \expandafter\tab@append\expandafter\tab@shortline\expandafter{%
132  \expandafter\tab@ckr\expandafter{\the\tab@columns}%
133  {\begingroup#1\endgroup}%

134 }%

135 \fi%

Finally, we say there was a rule here, so that default space gets put in after this. Otherwise we lose lots of generality.

136 \tab@ruletrue%

137 }

\tabspctype We need to work out which space-state we should be in. Then we just put the text in. Easy, really.

138 \def\tabspctype#1{\tab@extracol\tabspctype@i{#1}}%
139 \def\tabspctype@i#1{%
140 \tab@rulefalse%
141 \ifnum\tab@state>\tab@prespcstate%
142  \tab@setstate\tab@postspcstate%
143  \let\tab@tok\tab@posttext%
144 \else%
145  \tab@setstate\tab@prespcstate%
146  \let\tab@tok\tab@pretext%
147 \fi%
148 \tab@append\tab@tok{\begingroup#1\endgroup}%

149 }

\tabcoltype If we’re already in the column state, we bump the state and loop round again, to get all the appropriate default behaviour. We bump the column counter, and add the bits of text we were given to appropriate token lists. We also add the \hfil glue to the shortline list, to space out the rules properly.

150 \def\tabcoltype#1#2{%
151 \ifnum\tab@state=\tab@colstate%
152  \global\advance\tab@state\@ne%
153 \fi%

154 \advance\tab@columns\@ne%
155 \tab@setstate\tab@colstate%
156 \tab@append\tab@pretext{#1}%
157 \tab@append\tab@posttext{#2}%
158 \tab@append\tab@shortline{\hfil}%

159 }

\tabuserpretype \tabuserposttype These are both utterly trivial.
160 \def\tabuserpretype#1{%
161 \tab@setstate\tab@prestate%
162 \tab@prepend\tab@userpretext{#1}%

163 }

164 \def\tabuserposttype#1{%
165 \tab@setstate\tab@poststate%
166 \tab@prepend\tab@posttext{#1}%

167 }

2.8 The colset stack

Let’s start with something fairly easy. We’ll keep a stack of column sets so that users don’t get confused by package authors changing the current column set. This is fairly easy, really.

\tab@push \tab@pop \tab@head These are the stack management routines. The only important thing to note is that \tab@head must take place only in TEX’s mouth, so we can use it in

 \csname. . . \endcsname constructions.
168 \def\tab@push#1#2{%
169 \toks@{{#2}}%
170 \expandafter\def\expandafter#1\expandafter{\the\expandafter\toks@#1}%

171 }

172 \def\tab@pop#1{\expandafter\def\expandafter#1\expandafter{\@gobble#1}}
173 \def\tab@head#1{\expandafter\tab@head@i#1\relax}
174 \def\tab@head@i#1#2\relax{#1}

\colset \colpush \colpop Now we can define the user macros.

175 \def\tab@colstack{{tabular}}
176 \def\colset{\colpop\colpush}
177 \def\colpush{\tab@push\tab@colstack}
178 \def\colpop{\tab@pop\tab@colstack}

\tab@colset Now we define a shortcut for reading the top item off the stack.

179 \def\tab@colset{\tab@head\tab@colstack}

2.9 The main parser routine

\tab@initread This macro sets up lots of variables to their normal states prior to parsing a preamble. Some things may need changing, but not many.

180 \def\tab@initread{%

First, reset the parser state to the start state.

181 \global\tab@state\tab@startstate%

We clear the token lists to sensible values, mostly. The midtext macro contains what to put in the very middle of each template – \multicolumn will insert its argument here.

182 \tab@preamble{}%
183 \tab@shortline{}%
184 \def\tab@tabtext{&}%
185 \def\tab@midtext{\ignorespaces####\@maybe@unskip}%
186 \tab@pretext{}%
187 \tab@userpretext{}%
188 \tab@posttext{}%
189 \let\tab@multicol\@empty%
190 \def\tab@startpause{\penalty\postdisplaypenalty\medskip}%
191 \def\tab@endpause{\penalty\predisplaypenalty\medskip}%

Finally, reset the column counter, don’t raise errors when we loop, and set some parser flags to their appropriate values.

192 \tab@columns\z@%
193 \let\tab@looped\relax%
194 \tab@ruletrue%
195 \tab@initruletrue%
196 \tab@firstcoltrue%

197 }

\tab@readpreamble This is the main macro for preamble handling. Actually, all it does is gobble its argument’s leading brace and call another macro, but it does it with style.

198 \def\tab@readpreamble#1{%
199 \tab@doreadpream{#1}%
200 \iftab@initrule\global\tab@state\tab@prespcstate\fi%
201 \tab@setstate\tab@rulestate%
202 \tab@commit%

203 }

\tab@doreadpream The preamble is in an argument. Previous versions used a nasty trick using \let and \afterassignment. Now we use an explicit end token, to allow dodgy column type handlers to scoop up the remaining preamble tokens and process them. Not that anyone would want to do that, oh no (see the ‘[’ type in the eqnarray environment ;-)).

204 \def\tab@doreadpream#1{\tab@mkpreamble#1\q@delim}

\tab@mkpreamble This is the main parser routine. It takes each token in turn, scrutinises it carefully,

and does the appropriate thing with it.

The preamble was given as an argument to \tab@doreadpream, and that has helpfully stripped off the initial { character. We need to pick off the next token (whatever it is) so we can examine it. We’ll use \futurelet so we can detect groups and things in funny places.

205 \def\tab@mkpreamble{\futurelet\@let@token\tab@mkpreamble@i}

If we find a space token, we’ll go off and do something a bit special, since spaces are sort of hard to handle. Otherwise we’ll do it in the old fashioned way.

206 \def\tab@mkpreamble@i{%
207 \ifx\@let@token\@sptoken%
208  \expandafter\tab@mkpreamble@spc%

209 \else%
210  \expandafter\tab@mkpreamble@ii%
211 \fi%

212 }

If we find a \@@endpreamble token, that’s it and we’re finished. We just gobble it and return. Otherwise, if it’s an open group character, we’ll complain because someone’s probably tried to put an argument in the wrong place. Finally, if none of the other things apply, we’ll deal with the character below.

213 \def\tab@mkpreamble@ii{%
214 \ifx\@let@token\q@delim%
215  \def\@tempa{\let\@let@token}%
216 \else%
217  \ifcat\bgroup\noexpand\@let@token%
218  \tab@err@oddgroup%
219  \def\@tempa##1{\tab@mkpreamble}%
220  \else%
221  \let\@tempa\tab@mkpreamble@iii%
222  \fi%
223 \fi%
224 \@tempa%

225 }

Handle a character. This involves checking to see if it’s actually defined, and then doing it. Doing things this way means we won’t get stranded in mid-preamble unless a package author has blown it.

226 \def\tab@mkpreamble@iii#1{%
227 \@ifundefined{\tab@colset!col.\string#1}{%
228  \tab@err@undef{#1}\tab@mkpreamble%
229 }{%
230  \@nameuse{\tab@colset!col.\string#1}%

231 }% 232 }

If we get given a space character, we’ll look up the command name as before. If no-one’s defined the column type we’ll just skip it silently, which lets users do pretty formatting if they like.

233 \@namedef{tab@mkpreamble@spc} {%
234 \@ifundefined{\tab@colset!col. }{%
235  \tab@mkpreamble%
236 }{%
237  \@nameuse{\tab@colset!col. }%

238 }% 239 }

\coldef Here’s how to define column types the nice way. Some dexterity is required to make everything work right, but it’s simple really.

240 \def\coldef{\@ifnextchar[\coldef@i{\coldef@i[\tab@colset]}}
241 \def\coldef@i[#1]#2#3#{\coldef@ii[#1]{#2}{#3}}
242 \def\coldef@ii[#1]#2#3#4{%
243 \expandafter\def\csname#1!col.\string#2\endcsname#3{%
244  #4\tab@mkpreamble%

245 }% 246 }

\collet We’d like to let people copy column types from other places. This is how to do it.

247 \def\collet{\@ifnextchar[\collet@i{\collet@i[\tab@colset]}}
248 \def\collet@i[#1]#2{%
249 \@ifnextchar=%
250  {\collet@ii[#1]{#2}}%
251  {\collet@ii[#1]{#2}=}%

252 }

253 \def\collet@ii[#1]#2={%
254 \@ifnextchar[%
255  {\collet@iii[#1]{#2}}%
256  {\collet@iii[#1]{#2}[\tab@colset]}%

257 }

258 \def\collet@iii[#1]#2[#3]#4{%
259 \expandafter\let\csname#1!col.\string#2\expandafter\endcsname%
260  \csname#3!col.\string#4\endcsname%

261 }

\newcolumntype We just bundle the text off to \newcommand and expect it to cope. It ought to. The column type code inserts the user’s tokens directly, rather than calling \tab@doreadpream recursively. The magic control sequence is the one looked up by the parser.

There’s some additional magic here for compatiblity with the obscure way that array works.

262 \def\newcolumntype#1{\@ifnextchar[{\nct@i{#1}}{\nct@i#1[0]}}
263 \def\nct@i#1[#2]{\@ifnextchar[{\nct@ii{#1}[#2]}{\nct@iii{#1}{[#2]}}}
264 \def\nct@ii#1[#2][#3]{\nct@iii{#1}{[#2][#3]}}
265 \def\nct@iii#1#2#3{%
266 \expandafter\let\csname\tab@colset!col.\string#1\endcsname\relax%
267 \expandafter\newcommand\csname\tab@colset!col.\string#1\endcsname#2{%
268  \tab@deepmagic{#1}%
269  \tab@mkpreamble%
270  #3%

271 }% 272 }

Now for some hacking for compatibility with tabularx.

273 \def\newcol@#1[#2]{\nct@iii{#1}{[#2]}}

And now some more. This is seriously deep magic. Hence the name.

274 \def\tab@deepmagic#1{%
275 \csname NC@rewrite@\string#1\endcsname\NC@find\tab@@magic@@%

276 }

277 \def\NC@find#1\tab@@magic@@{}

2.10 Standard column types

First, make sure we’re setting up the right columns. This also sets the default for the user. Other packages must not use the \colset command for defining columns – they should use the stack operations defined above.

278 \colset{tabular}

Now do the simple alignment types. These are fairly simple. The mysterious kern in the ‘l’ type is to stop the \col@sep glue from vanishing due to the \unskip inserted by the standard \tab@midtext if the column contains no text. (Thanks for spotting this bug go to that nice Mr Carlisle.)

279 \coldef l{\tabcoltype{\kern\z@\tab@bgroup}{\tab@egroup\hfil}}
280 \coldef c{\tabcoltype{\hfil\tab@bgroup}{\tab@egroup\hfil}}
281 \coldef r{\tabcoltype{\hfil\tab@bgroup}{\tab@egroup}}

Some extensions now. These are explicitly teextual or mathematical columns. Can be useful if you’re providing column types for other people. I’ve inserted a kern here for exactly the same reason as for the ‘l’ column type above.

282 \coldef T#1{\tab@aligncol{#1}{\tab@btext}{\tab@etext}}
283 \coldef M#1{\tab@aligncol{#1}{\tab@bmaths}{\tab@emaths}}
284 \def\tab@aligncol#1#2#3{%
285 \if#1l\tabcoltype{\kern\z@#2}{#3\hfil}\fi%
286 \if#1c\tabcoltype{\hfil#2}{#3\hfil}\fi%
287 \if#1r\tabcoltype{\hfil#2}{#3}\fi%

288 }

Now for the default rules.

289 \coldef |{\tabruletype{\vrule\@width\arrayrulewidth}}
290 \coldef !#1{\tabruletype{#1}}

Deal with ‘@’ expressions.

291 \coldef @#1{\tabspctype{#1}}

And the paragraph types. I’ve added things to handle footnotes here.

292 \coldef p#1{\tabcoltype%
293  {\savenotes\vtop\tab@bpar{#1}}%
294  {\tab@epar\spewnotes}}
295 \coldef m#1{\tabcoltype%
296  {\savenotes$\vcenter\tab@bpar{#1}}%
297  {\tab@epar$\spewnotes}}
298 \coldef b#1{\tabcoltype%
299  {\savenotes\vbox\tab@bpar{#1}}%
300  {\tab@epar\spewnotes}}

Phew. Only a few more left now. The user text ones.

301 \coldef >#1{\tabuserpretype{#1}}
302 \coldef <#1{\tabuserposttype{#1}}

The strange column type.

303 \coldef ##1#2{\tabcoltype{#1}{#2}}

And ‘*’, which repeats a preamble spec. This is really easy, and not at all like the original one.

304 \coldef *#1#2{%
305 \count@#1%
306 \loop\ifnum\count@>0\relax%
307  \tab@doreadpream{#2}%
308  \advance\count@\m@ne%
309 \repeat%

310 }

2.11 Paragraph handling

First of all, starting new paragraphs: the vbox token is already there, and we have the width as an argument.

\tab@bpar There are some gymnastics to do here to support lists which form the complete text of the parbox. One of the odd things I’ll do here is to not insert a strut on the first line: instead, I’ll put the text into a box register so that I can inspect it later. So that I have access to the height of the first line, I’ll use a \vtop – I can get at the final depth by using \prevdepth, so this seems to be the most general solution.

311 \def\tab@bpar#1{%
312 \bgroup%
313 \hsize#1\relax%
314 \@arrayparboxrestore%
315 \setbox\z@\vtop\bgroup
316 \global\@minipagetrue%
317 \everypar{%
318  \global\@minipagefalse%
319  \everypar{}%

320 }% 321 }

\tab@epar To end the paragraph, close the box. That sounds easy, doesn’t it? I need to space out the top and bottom of the box so that it looks as if struts have been applied.

322 \def\tab@epar{%

Anyway, I should end the current paragraph if I’m still in horizontal mode. A simple \par will do this nicely. I’ll also remove any trailing vertical glue (which may be left there by a list environment), because things will look very strange otherwise.

323 \ifhmode\par\fi%
324 \unskip%

Now I’ll look at the depth of the last box: if it’s less deep than my special strut, I’ll cunningly backpedal by a bit, and add a box with the appropriate depth. Since this will lie on the previous baseline, it won’t alter the effective height of the box.

325 \ifdim\prevdepth<\dp\@arstrutbox%
326  \kern-\prevdepth%
327  \nointerlineskip%
328  \vtop to\dp\@arstrutbox{}%
329 \fi%

I’ve finished the bottom of the box now: I’ll close it, and start work on the top again.

330 \egroup%

For top-alignment to work, the first item in the box must be another box. (This is why I couldn’t just set \prevdepth at the beginning.) If the box isn’t high enough, I’ll add a box of the right height and then kern backwards so that the ‘real’ first box ends up in the right place.

331 \ifdim\ht\z@<\ht\@arstrutbox%
332  \vbox to\ht\@arstrutbox{}%
333  \kern-\ht\z@%
334 \fi%
335 \unvbox\z@%
336 \egroup%

337 }

2.12 Gentle persuasion

To persuade longtable to work, we emulate some features of the array way of doing things. It’s a shame, but we have to do it, because longtable came first.

Note the horribleness with the grouping here. In order to get everything expanded at the right time, \@preamble just replaces itself with the (not expanded!) preamble string, using \the. This means that the preamble string must be visible in the group just above us. Now, longtable (and array for that matter) does \@mkpreamble immediately after opening a new group. So all we need to do is close that group, do our stuff, and reopen the group again. (Evil laughter…)

338 \def\@mkpream#1{%
339 \endgroup%
340 \colset{tabular}%
341 \tab@initread%
342 \def\tab@multicol{\@arstrut}%
343 \tab@preamble{\tab@multicol}%
344 \def\tab@midtext{\ignorespaces\@sharp\@sharp\@maybe@unskip}%
345 \tab@readpreamble{#1}%
346 \gdef\@preamble{\the\tab@preamble}%
347 \let\tab@bgroup\begingroup%
348 \let\tab@egroup\endgroup%
349 \begingroup%

350 }

2.13 Debugging

This macro just parses a preamble and displays it on the terminal. It means I can see whether the thing’s working.

351 \def\showpream#1{%
352 \tab@initread%
353 \tab@readpreamble{#1}%
354 \showthe\tab@preamble%
355 \showthe\tab@shortline%

356 }

A quick macro for showing column types.

357 \def\showcol#1{%
358 \expandafter\show\csname\tab@colset!col.\string#1\endcsname%

359 }

2.14 The tabular and array environments

This is where we define the actual environments which users play with.

2.14.1 The environment routines

The real work is done in the \@array macro later. We just set up lots (and I mean lots) of parameters first, and then call \@array.

\tab@array The \tab@array macro does most of the common array things.

360 \def\tab@array{%
361 \tab@width\z@%
362 \let\tab@bgroup\tab@bmaths%
363 \let\tab@egroup\tab@emaths%
364 \@tabarray%

365 }

\tab@btext \tab@bmaths \tab@etext \tab@emaths These macros contain appropriate things to use when typesetting text or maths macros. They’re all trivial. They’re here only for later modification by funny things like the smarray environment.

366 \def\tab@btext{\begingroup}
367 \def\tab@bmaths{$}
368 \def\tab@etext{\endgroup}
369 \def\tab@emaths{\m@th$}

array Now for the array environment. The ‘$’ signs act as a group, so we don’t need to do extra grouping this time. Closing the environment is easy.

370 \def\array{%
371 \col@sep\arraycolsep%
372 \let\tab@extrasep\arrayextrasep%
373 \tab@normalstrut%
374 \tab@array%

375 }

376 \def\endarray{%
377 \crcr%
378 \egroup%
379 \tab@right%
380 \tab@restorehlstate%

381 }

smarray Now for something a little different. The smarray environment gives you an array with lots of small text.

382 \def\smarray{%
383 \extrarowheight\z@%
384 \col@sep\smarraycolsep%
385 \let\tab@extrasep\smarrayextrasep%
386 \def\tab@bmaths{$\scriptstyle}%
387 \def\tab@btext{\begingroup\scriptsize}%
388 \setbox\z@\hbox{\scriptsize\strut}%
389 \dimen@\ht\z@\dimen\tw@\dp\z@\tab@setstrut%
390 \tab@array%

391 }

392 \let\endsmarray\endarray

\tabstyle This is a little hook that document designers can use to modify the appearance of tables throughout a document. For example, I’ve set it to make the text size \small in all tables in this document. Macro writers shouldn’t try to use it as a hook for their own evilness, though. I’ve used \providecommand to avoid nobbling an existing definition.

393 \providecommand\tabstyle{}

\@tabular The two tabular environments share lots of common code, so we separate that out. (This needs to be done better.) All we really do here is set up the \tab@bgroup and \tab@egroup to localise things properly, and then go.

394 \def\@tabular#1{%
395 \tabstyle%
396 \tab@width#1%
397 \let\tab@bgroup\tab@btext%
398 \let\tab@egroup\tab@etext%
399 \col@sep\tabcolsep%
400 \let\tab@extrasep\tabextrasep%
401 \tab@normalstrut%
402 \@tabarray%

403 }

tabular tabular* These environments just call a macro which does all the common stuff.

404 \def\tabular{\@tabular\z@}
405 \expandafter\let\csname tabular*\endcsname\@tabular
406 \let\endtabular\endarray
407 \expandafter\let\csname endtabular*\endcsname\endarray

2.14.2 Setting the strut height

\tab@setstrut We use a magical strut, called \@arstrut, which keeps the table from collapsing

around our heads. This is where we set it up.

It bases the array strut size on the given values of \dimen@ and \dimen\tw@, amended by various appropriate fiddle values added in by various people.

408 \def\tab@setstrut{%
409 \setbox\@arstrutbox\hbox{%
410  \vrule%
411  \@height\arraystretch\dimen@%
412  \@depth\arraystretch\dp\strutbox%
413  \@width\z@%

414 }% 415 }

\tab@normalstrut This sets the strut the normal way, from the size of \strutbox.

416 \def\tab@normalstrut{%
417 \dimen@\ht\strutbox\advance\dimen@\extrarowheight%
418 \dimen\tw@\dp\strutbox%
419 \tab@setstrut%

420 }

2.14.3 Setting up the alignment

The following bits are mainly for other packages to hook themselves onto.

421 \let\@arrayleft\relax%
422 \let\@arrayright\relax%

423 \def\@tabarray{%
424 \let\@arrayleft\relax%
425 \let\@arrayright\relax%
426 \@ifnextchar[\@array{\@array[c]}%

427 }

\@array The \@array macro does most of the real work for the environments. The first job is to set up the row strut, which keeps the table rows at the right height. We just take the normal strut box, and extend its height by the \extrarowheight length parameter.

428 \def\@array[#1]#2{%

Sort out the hline state variable. We’ll store the old value in a control sequence to avoid wasting any more count registers.

429 \edef\tab@restorehlstate{%
430  \global\tab@endheight\the\tab@endheight%
431  \gdef\noexpand\tab@hlstate{\tab@hlstate}%

432 }%

433 \def\tab@hlstate{n}%

Now we read the preamble. All the clever things we’ve already done are terribly useful here.

The \tab@setcr sets up \\ to be a newline even if users have changed it using something like \raggedright.

434 \colset{tabular}%
435 \tab@initread%
436 \def\tab@midtext{\tab@setcr\ignorespaces####\@maybe@unskip}%
437 \def\tab@multicol{\@arstrut\tab@startrow}%
438 \tab@preamble{\tab@multicol\tabskip\z@skip}%
439 \tab@readpreamble{#2}%

Set up the default tabskip glue. This is easy: there isn’t any.

440 \tab@leftskip\z@skip%
441 \tab@rightskip\z@skip%

Now set up the positioning of the table. This is put into a separate macro because it’s rather complicated.

442 \tab@setposn{#1}%

Now work out how to start the alignment.

443 \ifdim\tab@width=\z@%
444  \def\tab@halign{}%
445 \else%
446  \def\tab@halign{to\tab@width}%
447 \fi%

Finally, do all the normal things we need to do before an alignment. Note that we define \tabularnewline first, then set \\ from that (using \tab@setcr). Since \\ is reset in the \tab@midtext of every table cell, it becomes secondary to \tabularnewline. Doing things this way avoids the problems with declarations like \raggedright which redefine \\ in their own (usually rather strange) way, so you don’t need to mess about with things like the \PreserveBackslash command given in the L^{A}TEX Companion.

448 \lineskip\z@\baselineskip\z@%
449 \m@th%
450 \def\tabularnewline{\tab@arraycr\tab@penalty}%
451 \tab@setcr%
452 \let\par\@empty%
453 \everycr{}\tabskip\tab@leftskip%
454 \tab@left\halign\tab@halign\expandafter\bgroup%
455  \the\tab@preamble\tabskip\tab@rightskip\cr%

456 }

You’ve no doubt noticed the \tab@left and \tab@right macros above. These are set up here and elsewhere to allow other things to gain control at various points of the table (they include and take the place of the \@arrayleft and \@arrayright hooks in array, put in for delarray’s use.

2.14.4 Positioning the table

\tab@setposn This macro sets everything up for the table’s positioning. It’s rather long, but not

all that complicated. Honest.

First, we set up some defaults (for centring). If anything goes wrong, we just do the centring things.

457 \def\tab@setposn#1{%
458 \def\tab@left{%
459  \savenotes%
460  \leavevmode\hbox\bgroup$\@arrayleft\vcenter\bgroup%

461 }%

462 \def\tab@right{%
463  \egroup%
464  \m@th\@arrayright$\egroup%
465  \spewnotes%

466 }%

467 \global\tab@endheight\z@%

For the standard positioning things, we just do appropriate boxing things. Note that the dollar signs are important, since delarray might want to put its delimiters in here.

The \if@tempswa switch it used to decide if we’re doing an unboxed tabular. We’ll set it if we find an unbox-type position code, and then check that everything’s OK for this.

468 \@tempswafalse%
469 \let\tab@penalty\relax%
470 \if#1t%
471  \def\tab@left{%
472  \savenotes%
473  \leavevmode\setbox\z@\hbox\bgroup$\@arrayleft\vtop\bgroup%

474 }%

475  \def\tab@right{%
476  \egroup%
477  \m@th\@arrayright$\egroup%
478  \tab@raisebase%
479  \spewnotes%

480 }%

481  \gdef\tab@hlstate{t}%

482  \global\tab@endheight\ht\@arstrutbox%
483 \else\if#1b%
484  \def\tab@left{%
485  \savenotes%
486  \leavevmode\setbox\z@\hbox\bgroup$\@arrayleft\vbox\bgroup%

487 }%

488  \def\tab@right{%
489  \egroup%
490  \m@th\@arrayright$\egroup%
491  \tab@lowerbase%
492  \spewnotes%

493 }%

494  \gdef\tab@hlstate{b}%
495 \else%
496  \if#1L\@tempswatrue\fi%
497  \if#1C\@tempswatrue\fi%
498  \if#1R\@tempswatrue\fi%
499 \fi\fi%

Now for some tests to make sure we’re allowed to do the unboxing. We text for \@arrayleft being defined, because people trying to hook us won’t understand unboxed tabulars.

500 \if@tempswa\ifhmode%
501  \ifinner\tab@err@unbrh\@tempswafalse\else\par\fi%
502 \fi\fi%
503 \if@tempswa\ifmmode\tab@err@unbmm\@tempswafalse\fi\fi%
504 \if@tempswa\ifx\@arrayleft\relax\else%
505  \tab@err@unbext\@tempswafalse%
506 \fi\fi%

Finally, if we’re still doing an unboxed alignment, we need to sort out the spacing. We know that no-one’s tried to hook on to the environment, so we clear

 \tab@left and \tab@right.
507 \if@tempswa%
508  \def\tab@left{\vskip\parskip\medskip}%
509  \def\tab@right{\par\@endpetrue\global\@ignoretrue}%

Now we need to sort out the alignment. The only way we can do this is by playing with tabskip glue. There are two possiblities:

• If this is a straight tabular or an array, we just use infinite glue. This is reasonable, I think.

• If we have a width for the table, we calculate the fixed values of glue on either side. This is fairly easy, and forces the table to the required width.

First, set up the left and right glues to represent the prevailing margins set up by list environments. I think this is the right thing to do.

510  \tab@leftskip\@totalleftmargin%
511  \tab@rightskip\hsize%
512  \advance\tab@rightskip-\linewidth%
513  \advance\tab@rightskip-\@totalleftmargin%

First of all, deal with the simple case. I’m using 10000 fill glue here, in an attempt to suppress \extracolsep glue from making the table the wrong width. It can always use filll glue if it really needs to, though.

514  \ifdim\tab@width=\z@%
515  \if#1L\else\advance\tab@leftskip\z@\@plus10000fill\fi%
516  \if#1R\else\advance\tab@rightskip\z@\@plus10000fill\fi%

Now for the fun bit. This isn’t too hard really. The extra space I must add around the table adds up to \linewidth − \tab@width. I just need to add this onto the appropriate sides of the table.

517  \else%
518  \dimen@\linewidth%
519  \advance\dimen@-\tab@width%
520  \if#1L\advance\tab@rightskip\dimen@\fi%
521  \if#1R\advance\tab@leftskip\dimen@\fi%
522  \if#1C%
523  \advance\tab@leftskip.5\dimen@%
524  \advance\tab@rightskip.5\dimen@%
525  \fi%
526  \fi%

Don’t allow page breaks. David Carlisle’s wonderful longtable package does page breaks far better than I could possibly do here, and we’re compatible with it (wahey!).

527  \def\tab@penalty{\penalty\@M}%

Finally, set the new width of the table, and leave.

528  \tab@width\hsize%
529 \fi%

530 }

2.14.5 Handling tops and bottoms

This is how the tops and bottoms of tables are made to line up with the text on the same line, in the presence of arbitrary rules and space. The old method, based on the way the array package worked, wasn’t terribly good. This new version copes much better with almost anything that gets thrown at it.

I’ll keep a state in a macro (\tab@hlstate), which tells me what I’m meant to be doing. The possible values are ‘n’, which means I don’t have to do anything, ‘t’, which means that I’m meant to be handling top-aligned tables, and ‘b’, which means that I’m meant to be lining up the bottom. There are several other ‘substates’ which have various magic meanings.

531 \def\tab@hlstate{n}

When all’s said and done, I extract the box containing the table, and play with the height and depth to try and make it correct.

\tab@addruleheight This macro is called by ‘inter-row’ things to add their height to our dimen register. Only do this if the state indicates that it’s sensible.

532 \def\tab@addruleheight#1{%
533 \if\tab@hlstate n\else%
534  \global\advance\tab@endheight#1\relax%

535 \fi%

536 }

\tab@startrow This is called at the start of a row, from within the array preamble. Currently, this assumes that the rows aren’t bigger than their struts: this is reasonable, although slightly limiting, and it could be done better if I was willing to rip the alignment apart and put it back together again.

537 \def\tab@startrow{%
538 \if\tab@hlstate t%
539  \gdef\tab@hlstate{n}%
540 \else\if\tab@hlstate b%
541  \global\tab@endheight\dp\@arstrutbox%
542 \fi\fi%

543 }

\tab@raisebase This macro is called at the end of it all, to set the height and depth of the box correctly. It sets the height to \tab@endheight, and the depth to everything else. The box is in \box 0 currently.

544 \def\tab@raisebase{%
545 \global\advance\tab@endheight-\ht\z@%
546 \raise\tab@endheight\box\z@%

547 }

\tab@lowerbase And, for symmetry’s sake, here’s how to set the bottom properly instead.

548 \def\tab@lowerbase{%
549 \global\advance\tab@endheight-\dp\z@%
550 \lower\tab@endheight\box\z@%

551 }

2.15 Breaking tables into bits

Unboxed tables have a wonderful advantage over boxed ones: you can stop halfway through and do something else for a bit. Here’s how:

\tabpause I’d like to avoid forbidding catcode changes here. I’ll use \doafter now I’ve got it, to ensure that colour handling and things occur inside the \noalign (otherwise they’ll mess up the alignment very seriously).

We have to be careful here to ensure that everything works correctly within lists. (The amsmath package had this problem in its \intertext macro, so I’m not alone here.)

552 \def\tabpause#{%
553 \noalign{\ifnum0=`}\fi%
554 \@parboxrestore%
555 \tab@startpause%
556 \vskip-\parskip%
557 \parshape\@ne\@totalleftmargin\linewidth%
558 \noindent%
559 \doafter\tabpause@i%

560 }

561 \def\tabpause@i{%
562 \nobreak%
563 \tab@endpause%

564 \ifnum0=`{\fi}%

565 }

2.16 The wonderful world of \multicolumn

\multicolumn This is actually fantasitcally easy. Watch and learn. Make sure you notice the \longs here: remember that some table cells can contain paragraphs, so it seems sensible to allow \par into the argument. (As far as I know, most other \multicolumn commands don’t do this, which seems a little silly. Then again, I forgot to do it the first time around.)

566 \long\def\multicolumn#1#2#3{%
567 \multispan{#1}%
568 \begingroup%
569  \tab@multicol%
570  \tab@initread%
571  \tab@preamble{}%
572  \long\def\tab@midtext{#3}%
573  \let\tab@looped\tab@err@multi%
574  \tab@readpreamble{#2}%
575  \the\tab@preamble%
576 \endgroup%
577 \ignorespaces%

578 }

2.17 Interlude: range lists

For processing arguments to \vgap and \cline, we need to be able to do things with lists of column ranges. To save space, and to make my fingers do less typing, here’s some routines which do range handling.

\ranges Given a macro name and a comma separated list of ranges and simple numbers, this macro will call the macro giving it each range in the list in turn. Single numbers n will be turned into ranges n–n.

The first job is to read the macro to do (which may already have some arguments attached to it). We’ll also start a group to make sure that our changes to temp registers don’t affect anyone else.

There’s a space before the delimiting \q@delim to stop numbers being parsed to far and expanding our quark (which will stop TEX dead in its tracks). Since we use \@ifnextchar to look ahead, spaces in range lists are perfectly all right.

579 \def\ranges#1#2{%
580 \gdef\ranges@temp{#1}%
581 \begingroup%
582 \ranges@i#2 \q@delim%

583 }

We’re at the beginning of the list. We expect either the closing marker (if this is an empty list) or a number, which we can scoop up into a scratch register.

584 \def\ranges@i{%
585 \@ifnextchar\q@delim\ranges@done{\afterassignment\ranges@ii\count@}%

586 }

We’ve read the first number in the range. If there’s another number, we’ll expect a ‘-’ sign to be next. If there is no ‘-’, call the user’s code with the number duplicated and then do the rest of the list.

587 \def\ranges@ii{%
588 \@ifnextchar-\ranges@iii{\ranges@do\count@\count@\ranges@v}%

589 }

Now we strip the ‘-’ off and read the other number into a temporary register.

590 \def\ranges@iii-{\afterassignment\ranges@iv\@tempcnta}

We have both ends of the range now, so call the user’s code, passing it both ends of the range.

591 \def\ranges@iv{\ranges@do\count@\@tempcnta\ranges@v}

We’ve finished doing an item now. If we have a ‘,’ next, then start over with the next item. Otherwise, if we’re at the end of the list, we can end happily. Finally, if we’re totally confused, raise an error.

592 \def\ranges@v{%
593 \@ifnextchar,%
594  \ranges@vi%

595 {%

596  \@ifnextchar\q@delim%
597  \ranges@done%
598  {\tab@err@range\ranges@vi,}%

599 }% 600 }

We had a comma, so gobble it, read the next number, and go round again.

601 \def\ranges@vi,{\afterassignment\ranges@ii\count@}

Here’s how we call the user’s code, now. We close the group, so that the user’s code doesn’t have to do global things to remember its results, and we expand the two range ends from their count registers. We also ensure that the range is the right way round.

602 \def\ranges@do#1#2{%
603 \ifnum#1>#2\else%
604  \expandafter\endgroup%
605  \expandafter\ranges@temp%
606  \expandafter{%
607  \the\expandafter#1%
608  \expandafter}%
609  \expandafter{%
610  \the#2%

611 }%

612  \begingroup%
613 \fi%

614 }

And finishing the scan is really easy. We close the group after gobbling the close token.

615 \def\ranges@done\q@delim{\endgroup}

\ifinrange Something a little more useful, now. \ifinrange takes four arguments: a number, a range list (as above), and two token lists which I’ll call then and else. If the number is in the list, I’ll do then, otherwise I’ll do else.

616 \def\ifinrange#1#2{%
617 \@tempswafalse%
618 \count@#1%
619 \ranges\ifinrange@i{#2}%
620 \if@tempswa%
621  \expandafter\@firstoftwo%
622 \else%
623  \expandafter\@secondoftwo%
624 \fi%

625 }

626 \def\ifinrange@i#1#2{%
627 \ifnum\count@<#1 \else\ifnum\count@>#2 \else\@tempswatrue\fi\fi%

628 }

2.18 Horizontal rules OK

This is where all the gubbins for \vgap and friends is kept, lest it contaminate fairly clean bits of code found elsewhere.

2.18.1 Drawing horizontal rules

\hline Note the funny use of \noalign to allow TEX stomach ops like \futurelet without starting a new table row. This lets us see if there’s another \hline coming up, so we can see if we need to insert extra vertical space.

629 \def\hline{%
630 \tab@dohline%
631 \noalign{\ifnum0=`}\fi%
632 \tab@penalty%
633 \futurelet\@let@token\hline@i%

634 }

We check here for another \hline command, and insert glue if there is. This looks terrible, though, and \hlx{hvh} is much nicer. Still…

635 \def\hline@i{%
636 \ifx\@let@token\hline%
637  \vskip\doublerulesep%
638  \tab@addruleheight\doublerulesep%
639 \fi%
640 \ifnum0=`{\fi}%

641 }

\tab@dohline This is where hlines actually get drawn. Drawing lines is more awkward than it used to be, particularly in unboxed tables. It used to be a case simply of saying \noalign{\hrule}. However, since unboxed tables are actually much wider than they look, this would make the rules stretch right across the page and look generally horrible.

The solution is simple: we basically do a dirty big \cline.

642 \def\tab@dohline{%
643 \multispan{\tab@columns}%

644 \leaders\hrule\@height\arrayrulewidth\hfil%
645 \tab@addruleheight\arrayrulewidth%
646 \cr%

647 }

2.18.2 Vertical rules

I couldn’t fit these in anywhere else, so they’ll have to go here. I’ll provide a new optional argument which specifies the width of the rule; this gets rid of the problem described in the Companion, where to get an unusually wide vertical rule, you have to play with things like \vrule width ⟨dimen⟩ which really isn’t too nice.

\vline The new \vline has an optional argument which gives the width of the rule. The \relax stops TEX trying to parse a ⟨rule-specification⟩ for too long, in case someone says something like ‘\vline depthcharges’ or something equally unlikely.

648 \renewcommand\vline[1][\arrayrulewidth]{\vrule\@width#1\relax}

2.18.3 Drawing bits of lines

Just for a bit of fun, here’s an extended version of \cline which takes a list of columns to draw lines under, rather than just a single range.

\cline Not a single line of code written yet, and we already have a dilemma on our hands. Multiple consecutive \cline commands are meant to draw on the same vertical bit of table. But horizontal lines are meant to have thickness now. Oh, well [sigh], we’ll skip back on it after all.

Now the problem remains how best to do the job. The way I see it, there are three possibilities:

• We can start a table row, and then for each column of the table (as recorded in \tab@columns) we look to see if that column is listed in the range list and if so draw the rule. This requires lots of scanning of the range list.

• We can take each range in the list, and draw rules appropriately, just like the old \cline used to do, and starting a new table row for each.

• We can start a table row, and then for each range remember where we stopped drawing the last row, move to the start of the new one, and draw it. If we start moving backwards, we close the current row and open a new one.

The last option looks the most efficient, and the most difficult. This is therefore what I shall do ;-).

The first thing to do is to add in a little negative space, and start a table row (omitting the first item). Then scan the range list, and finally close the table row and add some negative space again.

We need a global count register to keep track of where we are. Mixing local and global assignments causes all sorts of tragedy, so I shall hijack \tab@state.

649 \def\cline#1{%
650 \noalign{\kern-.5\arrayrulewidth\tab@penalty}%

651 \omit%
652 \global\tab@state\@ne%
653 \ranges\cline@i{#1}%
654 \cr%
655 \noalign{\kern-.5\arrayrulewidth\tab@penalty}%

656 }

Now for the tricky bit. When we’re given a range, we look to see if the first number is less than \tab@state. If so, we quickly close the current row, kern backwards and start again with an \omit and reset \tab@state to 1, and try again.

657 \def\cline@i#1#2{%
658 \ifnum#1<\tab@state\relax%
659  \tab@@cr%
660  \noalign{\kern-\arrayrulewidth\tab@penalty}%
661  \omit%
662  \global\tab@state\@ne%
663 \fi%

We are now either at or in front of the column position required. If we’re too far back, we must \hfil&\omit our way over to the correct column.

664 \@whilenum\tab@state<#1\do{%
665  \hfil\tab@@tab@omit%
666  \global\advance\tab@state\@ne%

667 }%

We’ve found the start correctly. We must deal with a tiny problem now: if this is not the first table cell, the left hand vertical rule is in the column to the left, so our horizontal rule won’t match up properly. So we skip back by a bit to compensate. If there isn’t actually a vertical rule to line up with, no-one will notice, because the rules are so thin. This adds a little touch of quality to the whole thing, which is after all the point of this whole exercise.

668 \ifnum\tab@state>\@ne%
669  \kern-\arrayrulewidth%
670 \fi%

Now we must stretch this table cell to the correct width.

671 \@whilenum\tab@state<#2\do{%
672  \tab@@span@omit%
673  \global\advance\tab@state\@ne%

674 }%

We’re ready. Draw the rule. Note that this is \hfill glue, just in case we start putting in \hfil glue when we step onto the next cell.

675 \leaders\hrule\@height\arrayrulewidth\hfill%

676 }

Some alignment primitives are hidden inside macros so they don’t get seen at the wrong time. This is what they look like:

677 \def\tab@@cr{\cr}
678 \def\tab@@tab@omit{&\omit}
679 \def\tab@@span@omit{\span\omit}

2.18.4 Drawing short table rows

Before I start on a description of more code, I think I’ll briefly discuss my reasons for leaving the \vgap command in its current state. There’s a reasonable case for introducing an interface between \vgap and \multicolumn, to avoid all the tedious messing about with column ranges. There are good reasons why I’m not going to do this:

• It’s very difficult to do: it requires either postprocessing of the table or delaying processing of each row until I know exactly what’s in it; a \multicolumn in a row should be able to affect a \vgap before the row, which gets very nasty. This package is probably far too large already, and adding more complexity and running the risk of exhausting TEX’s frustratingly finite capacity for the sake of relieving the user of a fairly trivial job doesn’t seem worthwhile.

• Perhaps more importantly, there are perfectly valid occasions when it’s useful to have the current vgap behaviour. For example, the MIX word layout diagrams found in The Art of Computer Programming use the little ‘stub lines’ to show where data items cross byte boundaries:

empty  − 1 0 0 0 0
occupied  + LINK      KEY

That’s my excuses out of the way; now I’ll press on with the actual programming.

\tab@checkrule We have a range list in \tab@xcols and a number as an argument. If we find the number in the list, wejust space out the following group, otherwise we let it be.

680 \def\tab@checkrule#1{%
681 \count@#1\relax%
682 \expandafter\ifinrange%
683 \expandafter\count@%
684 \expandafter{\tab@xcols}%
685  {\tab@checkrule@i}%
686  {}%

687 }

688 \def\tab@checkrule@i#1{\setbox\z@\hbox{#1}\hb@xt@\wd\z@{}}

\vgap We must tread carefully here. A single misplaced stomach operation can cause error messages. We therefore start with an \omit so we can search for optional arguments.

So that \hlx can get control after \vgap has finished, we provide a hook called \vgap@after which is expanded after \vgap has finished. Here we make it work like \@empty, which expands to nothing. (Note that \relax will start a new table row, so we can’t use that.) There are some penalty items here to stick the \vgap row to the text row and \hline that are adjacent to it. The longtable package will split an \hline in half, so this is the correct thing to do.

689 \def\vgap{%
690 \noalign{\nobreak}%
691 \omit%

692 \global\let\vgap@after\@empty%
693 \iffalse{\fi\ifnum0=`}\fi%
694 \@ifnextchar[\vgap@i\vgap@simple%

695 }

We set up two different sorts of \vgap – a simple one which allows all rules to be passed through, and a specific one which carefully vets each one (and is therefore slower). We decide which to so based on the presence of an optional argument.

The optional argument handler just passes its argument to an interface routine which is used by \hlx.

696 \def\vgap@i[#1]{\vgap@spec{#1}}

Now we handle specified columns. Since we’re in an omitted table cell, we must set things up globally. Assign the column spec to a macro, and set up vetting by the routine above. Then just go and do the job.

697 \def\vgap@spec#1#2{%
698 \gdef\tab@xcols{#1}%
699 \global\let\tab@ckr\tab@checkrule%
700 \vgap@do{#2}%

701 }

Handle all columns. Just gobble the column number for each rule, and let the drawing pass unharmed. Easy.

702 \def\vgap@simple#1{%
703 \global\let\tab@ckr\@gobble%
704 \vgap@do{#1}%

705 }

This is where stuff actually gets done. We set the \vgap flag on while we do the short row. Then just expand the token list we built while scanning the preamble.

Note that the flag is cleared at the end of the last column, to allow other funny things like \noalign and \omit before a new row is started.

706 \def\vgap@do#1{%
707 \ifnum0=`{}\fi%
708 \global\tab@vgaptrue%
709 \the\tab@shortline%
710  \vrule\@height#1\@width\z@%
711  \global\tab@vgapfalse
712  \tab@addruleheight{#1}%
713  \cr%
714 \noalign{\nobreak}%
715 \vgap@after%

716 }

2.18.5 Prettifying syntax

\hlx This is like a poor cousin to the preamble parser. The whole loop is carefully written to take place only in TEX’s mouth, so the alignment handling bits half way down the gullet don’t see any of this.

First, pass the string to another routine.

717 \def\hlx#1{\hlx@loop#1\q@delim}

Now peel off a token, and dispatch using \csname. We handle undefinedness of the command in a fairly messy way, although it probably works. Maybe.

718 \def\hlx@loop#1{%
719 \ifx#1\q@delim\else%
720  \@ifundefined{hlx@cmd@\string#1}{%
721  \expandafter\hlx@loop%
722  }{%
723  \csname hlx@cmd@\string#1\expandafter\endcsname%

724 }%

725 \fi%

726 }

\hlxdef New \hlx commands can be defined using \hlxdef. This is a simple abbreviation.

727 \def\hlxdef#1{\@namedef{hlx@cmd@#1}}

\hlx h Handle an ‘h’ character. Just do an \hline and return to the loop. We look ahead to see if there’s another ‘h’ coming up, and if so insert two \hline commands. This strange (and inefficient) behaviour keeps packages which redefine \hline happy.

728 \hlxdef h#1{%
729 \noalign{%
730  \ifx#1h%
731  \def\@tempa{\hline\hline\hlx@loop}%
732  \else%
733  \def\@tempa{\hline\hlx@loop#1}%
734  \fi%
735  \expandafter

736 }%

737 \@tempa%

738 }

\hlx b The ‘b’ character does a nifty backspace, for longtable’s benefit.

739 \hlxdef b{\noalign{\kern-\arrayrulewidth}\hlx@loop}

\hlx / The ‘/’ character allows a page break at the current position.

740 \hlxdef /{%
741 \noalign{\ifnum0=`}\fi%
742 \@ifnextchar[\hlx@cmd@break@i{\hlx@cmd@break@i[0]}%

743 }

744 \def\hlx@cmd@break@i[#1]{\pagebreak[0]\ifnum0=`{\fi}\hlx@loop}

\hlx v Handle a ‘v’ character. This is rather like the \vgap code above, although there are syntactic differences.

745 \hlxdef v{%
746 \noalign{\nobreak}%
747 \omit%
748 \iffalse{\fi\ifnum0=`}\fi%
749 \global\let\vgap@after\hlx@loop%
750 \@ifnextchar[\hlx@vgap@i{\hlx@vgap@ii\vgap@simple}%

751 }

752 \def\hlx@vgap@i[#1]{%
753 \ifx!#1!%
754  \def\@tempa{\hlx@vgap@ii\vgap@simple}%

755 \else%
756  \def\@tempa{\hlx@vgap@ii{\vgap@spec{#1}}}%
757 \fi%
758 \@tempa%

759 }

760 \def\hlx@vgap@ii#1{%
761 \@ifnextchar[{\hlx@vgap@iii{#1}}{\hlx@vgap@iii{#1}[\doublerulesep]}%

762 }

763 \def\hlx@vgap@iii#1[#2]{#1{#2}}

\hlx s Allow the user to leave a small gap using the ‘s’ command.

764 \hlxdef s{%
765 \noalign{\ifnum0=`}\fi%
766 \nobreak%
767 \@ifnextchar[\hlx@space@i{\hlx@space@i[\doublerulesep]}%

768 }

769 \def\hlx@space@i[#1]{%
770 \vskip#1%
771 \tab@addruleheight{#1}%
772 \ifnum0=`{\fi}%
773 \hlx@loop%

774 }

\hlx c We might as well allow a ‘c’ command to do a \cline.

775 \hlxdef c#1{\cline{#1}\hlx@loop}

\hlx . The ‘.’ character forces a start of the new column. There’s a little problem here. Since the ‘.’ character starts the next column, we need to gobble any spaces following the \hlx command before the cell contents actually starts. Unfortunately, \ignorespaces will start the column for us, so we can’t put it in always. We’ll handle it here, then. We’ll take the rest of the ‘preamble’ string, and warn if it’s not empty. Then we’ll \ignorespaces – this will start the column for us, so we don’t need to \relax any more.

776 \hlxdef .#1\q@delim{%
777 \ifx @#1@\else%
778  \PackageWarning{mdwtab}{%
779  Ignoring \protect\hlx\space command characters following a
780  `.'\MessageBreak command%

781 }%

782 \fi%
783 \ignorespaces%

784 }

2.19 Starting new table rows

We take a break from careful mouthery at last, and start playing with newlines. The standard one allows pagebreaks in unboxed tables, which isn’t really too desirable.

Anyway, we’ll try to make this macro rather more reusable than the standard one. Here goes.

\@arraycr We pass lots of information to a main parser macro, and expect it to cope.

785 \def\@arraycr{\tab@arraycr{}}
786 \def\tab@arraycr#1{\tab@cr{\tab@tabcr{#1}}{}{}}

Now to actually do the work. \tab@cr passes us the skip size, and the appropriate one of the two arguments given above (both of which are empty) depending on the presence of the ∗.

787 \def\tab@tabcr#1#2{%

If the total height I need to add between rows (from the optional argument and the ‘extrasep’ parameter) is greater than zero, I’ll handle this by extending the strut slightly. I’m not actually sure whether this is the right thing to do, to be honest, although it’s easier than trying to to an automatic \vgap, because I need to know which columns to skip. If the space is less than zero, I’ll just insert the vertical space with in a \noalign.

First, to calculate how much space needs adding.

788 \dimen@#2%
789 \advance\dimen@\tab@extrasep%

If the height is greater than zero, I need to play with the strut. I must bear in mind that the current table cell (which I’m still in, remember) may be in vertical mode, and I may or may not be in a paragraph.

If I am in vertical mode, I’ll backpedal to the previous box and put the strut in an hbox superimposed on the previous baseline. Otherwise, I can just put the strut at the end of the text. (This works in either LR or paragraph mode as long as I’m not between paragraphs.)

790 \ifdim\dimen@>\z@%
791  \ifvmode%
792  \unskip\kern-\prevdepth%
793  \nointerlineskip\expandafter\hbox%
794  \else%
795  \@maybe@unskip\expandafter\@firstofone%
796  \fi%
797  {\advance\dimen@\dp\@arstrutbox\vrule\@depth\dimen@\@width\z@}%
798 \fi%

This table cell works as a group (which is annoying here). I’ll copy the interrow gap into a global register so that I can use it in the \noalign.

799 \global\dimen\@ne\dimen@%
800 \cr%
801 \noalign{%
802  #1%
803  \ifdim\dimen\@ne<\z@\vskip\dimen\@ne\relax\fi%

804 }%

805 \@gobble%

806 }

\tab@setcr To set the \\ command correctly in each table cell, we make it a part of the preamble (in \tab@midtext) to call this routine. It’s easy – just saves the preamble from being huge.

807 \def\tab@setcr{\let\\\tabularnewline}

\tab@cr Now we do the parsing work. This is fun. Note the revenge of the funny braces here. Nothing to worry about, honest. The tricky bit is to keep track of which arguments are which. (Thanks to David Carlisle for pointing out that I’d missed out the \relax here.)

808 \def\tab@cr#1#2#3{%
809 \relax%
810 \iffalse{\fi\ifnum0=`}\fi%
811 \@ifstar{\tab@cr@i{#1}{#3}}{\tab@cr@i{#1}{#2}}%

812 }

813 \def\tab@cr@i#1#2{%
814 \@ifnextchar[{\tab@cr@ii{#1}{#2}}{\tab@cr@ii{#1}{#2}[\z@]}%

815 }

816 \def\tab@cr@ii#1#2[#3]{%
817 \ifnum0=`{}\fi%
818 #1{#3}{#2}%

819 }

2.20 Gratuitous grotesquery

So far we’ve had an easy-ish ride (or should that be queasy?). Now for something unexplainably evil. We convince L^{A}TEX that it’s loaded the array package, so that packages which need it think they’ve got it.

The bogus date is the same as the date for the array package I’ve got here – this will raise a warning if Frank updates his package which should filter back to me telling me that there’s something I need to know about.

The messing with \xdef and the funny parsing ought to insert the current mdwtab version and date into the fake array version string, giving a visible clue to the user that this isn’t the real array package.

820 \begingroup
821 \catcode`.=11
822 \def\@tempa#1 #2 #3\@@{#1 #2}
823 \xdef\ver@array.sty
824 {1995/11/19 [mdwtab.sty \expandafter\@tempa\ver@mdwtab.sty\@@]}
825 \endgroup

2.21 Error messages

I’ve put all the error messages together, where I can find them, translate them or whatever.

First, some token-space saving (which also saves my fingers):

826 \def\tab@error{\PackageError{mdwtab}}

Now do the error messages.

827 \def\tab@err@misscol{%
828 \tab@error{Missing column type}{%
829  I'm lost.  I was expecting something describing^^J%
830  the type of the current column, but you seem to^^J%
831  have missed it out.  I've inserted a type `l'^^J%
832  column here in the hope that this makes sense.%

833 }% 834 }

835 \def\tab@err@oddgroup{%
836 \tab@error{Misplaced group in table preamble}{%
837  I've found an open brace character in your preamble^^J%
838  when I was expecting a specifier character.  I'm^^J%
839  going to gobble the whole group and carry on as if^^J%
840  I'd never seen it.%

841 }% 842 }

843 \def\tab@err@undef#1{%
844 \tab@error{Unknown `\tab@colset' preamble character `\string#1'}{%
845  I don't understand what you meant by typing this^^J%
846  character.  Anyway, I'll ignore it this time around.^^J%
847  Just don't you do it again.%

848 }% 849 }

850 \def\tab@err@unbrh{%
851 \tab@error{Can't use unboxed tabular in LR mode}{%
852  You've asked for a tabular or array environment with^^J%
853  `L', `C' or `R' as the position specifier, but you're^^J%
854  in LR (restricted horizontal) mode, so it won't work.^^J%
855  I'll assume you really meant `c' and soldier on.%

856 }% 857 }

858 \def\tab@err@unbmm{%
859 \tab@error{Can't use unboxed tabular in maths mode}{%
860  You've asked for a tabular or array environment with^^J%
861  `L', `C' or `R' as the position specifier, but you're^^J%
862  in maths mode, so it won't work.  I'll pretend that^^J%
863  you really typed `c', and that this is all a bad dream.%

864 }% 865 }

866 \def\tab@err@unbext{%
867 \tab@error{Can't extend unboxed tabulars}{%
868  You're trying to use kludgy extensions (e.g.,^^J%
869  `delarray') on an array or tabular with `L', `C'^^J%
870  or `R' as the position specifier.  I'll assume you^^J%
871  subconsciously wanted a `c' type all along.%

872 }% 873 }

874 \def\tab@err@multi{%
875 \tab@error{More than one column in a \protect\multicolumn}{%
876  You've put more than one column into a \string\multicolumn^^J%
877  descriptor.  It won't work.  I have no idea what^^J%
878  will happen, although it won't be pleasant.  Hold^^J%
879  on tight now...%

880 }% 881 }

882 \def\tab@err@range{%
883 \tab@error{Expected `,' or `<end>' in range list}{%
884  I was expecting either the end of the range list,^^J%
885  or a comma, followed by another range.  I've^^J%
886  inserted a comma to try and get me back on track.^^J%

887  Good luck.%

888 }% 889 }

That’s it. No more. Move along please.

890 ⟨/mdwtab⟩

3 Implementation of mathenv

This is in a separate package, mainly to avoid wasting people’s memory.

891 ⟨∗mathenv⟩

3.1 Options handling

We need to be able to cope with fleqn and leqno options. This will adjust our magic modified eqnarray environment appropriately.

892 \newif\if@fleqn
893 \newif\if@leqno
894 \DeclareOption{fleqn}{\@fleqntrue}
895 \DeclareOption{leqno}{\@leqnotrue}
896 \ProcessOptions

We use the mdwtab package for all its nice table handling things. (Oh, and to inflict it on users who want to do nice equations and don’t care about our tables.)

897 \RequirePackage{mdwtab}

3.2 Some useful registers

The old L^{A}TEX version puts the equation numbers in by keeping a count of where it is in the alignment. Since I don’t know how may columns there are going to be, I’ll just use a switch in the preamble to tell me to stop tabbing.

898 \newif\if@eqalast

Now define some useful length parameters. First allocate them:

899 \newskip\eqaopenskip
900 \newskip\eqacloseskip
901 \newskip\eqacolskip
902 \newskip\eqainskip
903 \newskip\splitleft
904 \newskip\splitright

Now assign some default values. Users can play with these if they really want although I can’t see the point myself.

905 \AtBeginDocument{%
906 \eqacloseskip\@centering%
907 \eqacolskip1.5em\@plus\@m\p@
908 \eqainskip\z@%
909 \if@fleqn%
910  \eqaopenskip\mathindent%
911  \splitleft\mathindent\relax%
912  \splitright\mathindent\@minus\mathindent\relax%

913 \else%
914  \eqaopenskip\@centering%
915  \splitleft2.5em\@minus2.5em%
916  \splitright\splitleft%
917 \fi%
918 \relax%

919 }

3.3 A little display handling

I’m probably going a little far here, and invading territory already claimed by the amsmath stuff (and done a good deal better than I can be bothered to do), but just for completeness, this is how we handle attempts to put displays inside other displays without screwing up the spacing.

\dsp@startouter This is how we start an outermost display. It’s fairly easy really. We make \dsp@start start an inner display, and make \dsp@end close the outer display.

920 \def\dsp@startouter{%
921 \let\dsp@end\dsp@endouter%
922 $$%

923 }

\dsp@endouter Ending the outer display is utterly trivial.

924 \def\dsp@endouter{$$}

\dsp@startinner Starting inner displays is done in a vbox (actually I choose \vbox or \vtop depending on the setting of leqno to put the equation number the right way round).

925 \def\dsp@startinner{%
926 \let\dsp@end\dsp@endinner%
927 \if@fleqn\kern-\mathindent\fi%
928 \if@leqno\vtop\else\vtop\fi\bgroup%

929 }

\dsp@endinner Ending an inner display is also really easy.

930 \def\dsp@endinner{\egroup}

\dsp@start This is what other bits of code uses to start displays. It’s one of the start macros up above, and outer by default.

931 \def\dsp@start{%
932 \ifmmode%
933  \ifinner\mth@err@mdsp\fi%
934  \expandafter\dsp@startinner%
935 \else%
936  \ifhmode\ifinner\mth@err@hdsp\fi\fi%
937  \expandafter\dsp@startouter%
938 \fi%

939 }

\dsp@tabpause This sets up the correct pre- and postambles for the \tabpause macro in maths displays. This is fairly simple stuff.

940 \def\dsp@tabpause{%

941 \def\tab@startpause%
942  {\penalty\postdisplaypenalty\vskip\belowdisplayskip}%
943 \def\tab@endpause%
944  {\penalty\predisplaypenalty\vskip\abovedisplayskip}%

945 }

3.4 The eqnarray environment

We allow the user to play with the style if this is really wanted. I dunno why, really. Maybe someone wants very small alignments.

946 \let\eqastyle\displaystyle

3.4.1 The main environments

eqnarray eqnarray* We define the toplevel commands here. They just add in default arguments and then call \@eqnarray with a preamble string. We handle equation numbers by setting up a default (\eqa@defnumber) which is put into the final column. At the beginning of each row, we globally \let \eqa@number equal to \eqa@defnumber. The \eqnumber macro just changes \eqa@number as required. Since \eqa@number is changed globally we must save it in this environment.

First, we must sort out the optional arguments and things. This is really easy. The only difference between the starred and non-starred environments is the default definition of \eqa@defnumber.

947 \def\eqnarray{%
948 \eqnarray@i\eqa@eqcount%

949 }

950 \@namedef{eqnarray*}{\eqnarray@i{}}
951 \def\eqnarray@i#1{\@ifnextchar[{\eqnarray@ii{#1}}{\eqnarray@ii{#1}[rcl]}}

Right. Now for the real work. The first argument is the default numbering tokens; the second is the preamble string.

952 \def\eqnarray@ii#1[#2]{%

Set up the equation counter and labels correctly.

\begin{rant}

The hacking with \@currentlabel is here because (in the author’s opinion) L^{A}TEX’s \refstepcounter macro is broken. It’s currently defined as

   \def\refstepcounter#1{%
     \stepcounter{#1}%
     \protected@edef\@currentlabel%
       {\csname p@#1\endcsname\csname the#1\endcsname}%
   }

which means that the current label gets ‘frozen’ as soon as you do the counter step. By redefining the macro as

   \def\refstepcounter#1{%
     \stepcounter{#1}%
     \edef\@currentlabel{%
       \expandafter\noexpand\csname p@#1\endcsname%
       \expandafter\noexpand\csname the#1\endcsname%
     }%
   }

these sorts of problems would be avoided, without any loss of functionality or compatibility that I can see.

\end{rant}
953 \stepcounter{equation}%
954 \def\@currentlabel{\p@equation\theequation}%

The next step is to set up the numbering. I must save the old numbering so I can restore it later (once in the alignment, I must assign these things globally).

955 \let\eqa@oldnumber\eqa@number%
956 \def\eqa@defnumber{#1}%
957 \global\let\eqa@number\eqa@defnumber%

The \if@eqalastfalse switch is false everywhere except when we’re in the final column.

958 \@eqalastfalse%

Remove the \mathsurround kerning, since it will look very odd inside the display. We have our own spacing parameters for configuring these things, so \mathsurround is unnecessary.

959 \m@th%

Time to parse the preamble string now. I must choose the correct column set, initialise the preamble parser and set up the various macros. The extra ‘@{\tabskip\eqacloseskip}’ item sets up the tabskip glue to centre the alignment properly.

960 \colset{eqnarray}%
961 \tab@initread%
962 \def\tab@tabtext{&\tabskip\z@skip}%
963 \tab@preamble{\tabskip\z@skip}%
964 \tab@readpreamble{#2@{\tabskip\eqacloseskip}}%
965 \dsp@tabpause%

Now for some final setting up. The column separation is set from the user’s parameter, the \everycr tokens are cleared, and I set up the newline command appropriately.

966 \col@sep.5\eqainskip%
967 \everycr{}%
968 \let\\\@eqncr%

Now start a maths display and do the alignment. Set up the left hand tabskip glue to centre the alignment, and do the actual alignment. The preamble used is mainly that generated from the user’s string, although the stuff at the end is how we set up the equation number – it repeats appropriately so we can always find it.

969 \dsp@start%
970 \tabskip\eqaopenskip%
971 \halign to\displaywidth\expandafter\bgroup%
972  \the\tab@preamble%
973  &&\eqa@lastcol\hb@xt@\z@{\hss##}\tabskip\z@\cr%

974 }

Now for the end of the environment. This is really easy. Set the final equation number, close the \halign, tidy up the equation counter (it’s been stepped once too many times) and close the display.

975 \def\endeqnarray{%
976 \eqa@eqnum%
977 \egroup%
978 \dsp@end%
979 \global\let\eqa@number\eqa@oldnumber%
980 \global\@ignoretrue%
981 \global\advance\c@equation\m@ne%

982 }

983 \expandafter\let\csname endeqnarray*\endcsname\endeqnarray

Now we can define the column types.

984 \colpush{eqnarray}

Note the positioning of ord atoms in the stuff below. This will space out relations and binops correctly when they occur at the edges of columns, and won’t affect ord atoms at the edges, because ords pack closely.

First the easy onces. Just stick \hfil in the right places and everything will be all right.

985 \coldef r{\tabcoltype{\hfil$\eqastyle}{{}$}}
986 \coldef c{\tabcoltype{\hfil$\eqastyle{}}{{}$\hfil}}
987 \coldef l{\tabcoltype{$\eqastyle{}}{$\hfil}}
988 \coldef x{\tabcoltype{\if@fleqn\else\hfil\fi$\eqastyle}{$\hfil}}

Now for the textual ones. This is also fairly easy.

989 \collet T [tabular]T

Sort of split types of equations. I mustn’t use \rlap here, or everything goes wrong – \\ doesn’t get noticed by TEX in the same way as \cr does.

990 \coldef L{\tabcoltype{\hb@xt@2em\bgroup$\eqastyle}{$\hss\egroup}}

The ‘:’ column type is fairly simple.

991 \coldef :{\tabspctype{\tabskip\eqacolskip}}
992 \coldef q{\tabspctype{\quad}}

The other column types just insert given text in an appropriate way.

993 \collet > [tabular]>
994 \collet < [tabular]<
995 \collet * [tabular]*
996 \collet @ [tabular]@

Finally, the magical ‘\magic’ column type, which sets the equation number. We set up the \tabskip glue properly, tab on, and set the flag which marks the final column. The \eqa@lastcol command is there to raise an error if the user tabs over to this column. I’ll temporarily redefine it to \@eqalasttrue when I enter this column legitimately. The extra magical bits here will make the final column repeat, so that we can find it if necessary. Well is this column type named.

That’s it. We can return to normal now.

997 \colpop

3.4.2 Newline codes

Newline sequences (\\) get turned into calls of \@eqncr. The job is fairly simple, really.

998 \def\@eqncr{\tab@cr\eqacr@i\interdisplaylinepenalty\@M}%
999 \def\eqacr@i#1#2{%
1000 \eqa@eqnum%
1001 \noalign{\penalty#2\vskip\jot\vskip#1}%

1002 }

3.4.3 Setting equation numbers

\eqa@eqpos Before we start, we need to generalise the flush-left number handling bits. The macro \eqa@eqpos will put its argument in the right place.

1003 \if@leqno
1004 \def\eqa@eqpos#1{%
1005  \hb@xt@.01\p@{}\rlap{\normalfont\normalcolor\hskip-\displaywidth#1}%

1006 }

1007 \else
1008 \def\eqa@eqpos#1{\normalfont\normalcolor#1}

1009 \fi

\eqa@eqnum Here we typeset an equation number in roughly the right place. First I’ll redefine \eqa@lastcol so that it tells me I’m in the right place, and start a loop to find that place.

1010 \def\eqa@eqnum{%
1011 \global\let\eqa@lastcol\@eqalasttrue%
1012 \eqa@eqnum@i%

1013 }

Now for the loop. The \relax here is absolutely vital – it starts the table column, inserting useful tokens like ‘\eqa@lastcol’ which tell me where I am in the alignment. Then, if I’ve reached the end, I can typeset the equation number; otherwise I go off into another macro and step on to the next column.

1014 \def\eqa@eqnum@i{%
1015 \relax%
1016 \if@eqalast%
1017  \expandafter\eqa@eqnum@ii%
1018 \else%
1019  \expandafter\eqa@eqnum@iii%
1020 \fi%

1021 }

1022 \def\eqa@eqnum@ii{%
1023 \eqa@eqpos\eqa@number%
1024 \global\let\eqa@number\eqa@defnumber%
1025 \global\let\eqa@lastcol\eqa@@lastcol%
1026 \cr%

1027 }

1028 \def\eqa@eqnum@iii{&\eqa@eqnum@i}

\eqa@lastcol This is used as a marker for the final column in an eqnarray environment. By default it informs the user that they’ve been very silly and swallows the contents of the column. I’ll redefine it to something more useful at appropriate times, and then turn it back again.

1029 \def\eqa@@lastcol{\mth@err@number\setbox\z@}
1030 \let\eqa@lastcol\eqa@@lastcol

3.4.4 Numbering control

\eqnumber The \eqnumber command sets the equation number on the current equation. This is really easy, actually.

1031 \newcommand\eqnumber[1][\eqa@eqcount]{\gdef\eqa@number{#1}}

\eqa@eqcount This is how a standard equation number is set, stepping the counter and all. It’s really easy and obvious.

1032 \def\eqa@eqcount{(\theequation)\global\advance\c@equation\@ne}

\nonumber The L^{A}TEX \nonumber command could be defined by saying

     \renewcommand{\nonumber}{\eqnumber[]}

but I’ll be slightly more efficient and redefine \eqa@number directly.

1033 \def\nonumber{\global\let\eqa@number\@empty}

3.4.5 The eqnalign environment

As a sort of companion to eqnarray, here’s an environment which does similar things inside a box, rather than taking up the whole display width. It uses the same column types that we’ve already created, so there should be no problems.

eqnalign First, sort out some simple things like optional arguments.

1034 \def\eqnalign{\@ifnextchar[\eqnalign@i{\eqnalign@i[rcl]}}
1035 \def\eqnalign@i[#1]{%
1036 \@ifnextchar[{\eqnalign@ii{#1}}{\eqnalign@ii{#1}[c]}%

1037 }

Now we actually do the environment. This is fairly easy, actually.

1038 \def\eqnalign@ii#1[#2]{%
1039 \let\\\eqn@cr%
1040 \colset{eqnarray}%
1041 \tab@initread%
1042 \def\tab@tabtext{&\tabskip\z@skip}%
1043 \tabskip\z@skip%
1044 \col@sep.5\eqainskip%
1045 \tab@readpreamble{#1}%
1046 \everycr{}%
1047 \if#2t\vtop\else%
1048  \if#2b\vbox\else%
1049  \vcenter%
1050  \fi%
1051 \fi%
1052 \bgroup%
1053 \halign\expandafter\bgroup\the\tab@preamble\cr%

1054 }

Finishing the environment is even simpler.

1055 \def\endeqnalign{%
1056 \crcr%
1057 \egroup%
1058 \egroup%

1059 }

\eqn@cr Newlines are really easy here.

1060 \def\eqn@cr{\tab@cr\eqn@cr@i{}{}}
1061 \def\eqn@cr@i#1{\cr\noalign{\vskip\jot\vskip#1}\@gobble}

3.5 Simple multiline equations

As a sort of example and abbreviation, here’s a multiline display environment which just centres everything.

eqlines We just get \eqnarray to do everything for us. This is really easy.

1062 \def\eqlines{\eqnarray[x]}
1063 \let\endeqlines\endeqnarray

eqlines* There’s a ∗ version which omits numbers. This is easy too. Lots of hacking with expansion here to try and reduce the number of tokens being used. Is it worth it?

1064 \expandafter\edef\csname eqlines*\endcsname{%
1065 \expandafter\noexpand\csname eqnarray*\endcsname[x]%

1066 }

1067 \expandafter\let\csname endeqlines*\expandafter\endcsname
1068  \csname endeqnarray*\endcsname

3.6 Split equations

Based on an idea from The TEXbook, we provide some simple environments for doing split equations. These’s plenty of scope for improvement here, though.

spliteqn spliteqn* The only difference between these two is that the ∗-version doesn’t put in an equa-

tion number by default (although this behaviour can be changed by \eqnumber).

The fun here mainly concerns putting in the equation number at the right place – for leqno users, we need to put the number on the first line; otherwise we put it on the last line.

The way we handle this is to have two macros, \\ (which clearly does all the user line breaks) and \seq@lastcr which is used at the end of the environment to wrap everything up. The \seq@eqnocr macro puts an equation number on the current line and then does a normal \\. It also resets \\ and \seq@lastcr so that they don’t try to put another equation number in. This must be done globally, although anyone who tries to nest maths displays will get what they deserve.

For the non-∗ environment, then, we need to step the equation counter, and set \\ to \seq@cr or \seq@eqnocr as appropriate for the setting of the leqno flag – \seq@lastcr always gets set to put an equation number in (because it will be reset if the number actually gets done earlier – this catches stupid users trying to put a single row into a split environment).

1069 \def\spliteqn{%

1070 \let\eqa@oldnumber\eqa@number%
1071 \global\let\eqa@number\eqa@eqcount%
1072 \spliteqn@i%

1073 }

For the ∗ variant, we don’t need to bother with equation numbering, so this is really easy.

1074 \@namedef{spliteqn*}{%
1075 \let\eqa@oldnumber\eqa@number%
1076 \gdef\eqa@number{}%
1077 \spliteqn@i%

1078 }

Ending the environments is easy. Most of the stuff here will be described later.

1079 \def\endspliteqn{%
1080 \hfilneg\seq@lastcr%
1081 \egroup%
1082 \dsp@end%
1083 \global\let\eqa@number\eqa@oldnumber%
1084 \global\advance\c@equation\m@ne%

1085 }

1086 \expandafter\let\csname endspliteqn*\endcsname\endspliteqn

\spliteqn@i Here we handle the full display splits. Start a maths display, and make each row

of the alignment take up the full display width.

The macro \seq@dosplit does most of the real work for us – setting up the alignment and so forth. The template column is interesting. There are two items glue on both sides of the actual text:

• Some glue which can shrink. This keeps the display from the edges of the page unless we get a really wide item.

• An \hfil to do the alignment. By default, this centres the equations. On the first line, however, we put a leading \hfilneg which cancels the first \hfil, making the first row left aligned. Similarly, at the end, we put an \hfilneg after the last equation to right align the last line.

We pass this information on as an argument. It’s easy really.

1087 \def\spliteqn@i{%

First, set up equation numbering properly. See my rant about

\refstepcounter above.
1088 \stepcounter{equation}%
1089 \def\@currentlabel{\p@equation\theequation}%

Right; now to sort out the numbering and newline handling. If the number’s meant to be on the first line (for leqno users), then it gets typeset on the first like; otherwise we just do a normal newline on all lines except the first. Once \seq@eqnocr has done its stuff, it redefines all the newline handling not to insert another number.

1090 \if@leqno%
1091  \global\let\seq@docr\seq@eqnocr%
1092 \else%

1093  \global\let\seq@docr\seq@cr%
1094 \fi%
1095 \global\let\seq@lastcr\seq@eqnocr%

For my next trick, I’ll do some display handling – start a (possibly nested) maths display, set up the \tabpause macro appropriately, and set the newline command to do the right thing.

1096 \dsp@start%
1097 \dsp@tabpause%
1098 \def\\{\seq@docr}%

Finally, call another macro to do the remaining bits of setting up.

1099 \seq@dosplit%
1100  {\hb@xt@\displaywidth{%
1101  \hskip\splitleft\hfil$\displaystyle##$%
1102  \hfil\hskip\splitright}}%
1103  {\hfilneg}%

1104 }

subsplit For doing splits in the middle of equations, we provide a similar environment. Here, we make \\ just start a new line. We also use a \vcenter rather than a full maths display. The glue items are also a bit different: we use plain double-quads on each side of the item, and we need to remove them by hand at the extremeties of the environment.

1105 \def\subsplit{%
1106 \let\\\seq@cr%
1107 \vcenter\bgroup%
1108 \seq@dosplit{\hfil\qquad$##$\qquad\hfil}{\hfilneg\hskip-2em}%

1109 }

Ending the environment is fairly easy. We remove the final glue item, and close the alignment and the vbox.

1110 \def\endsubsplit{%
1111 \hfilneg\hskip-2em\cr%
1112 \egroup\egroup%

1113 }

\seq@dosplit Here we do most of the real work. Actually, since the preamble is passed in as an argument, most of the work is already done. The only thing to really note is the template for subsequent columns. To stop users putting in extra columns (which is where we put the equation number) we raise an error and discard the input in a scratch box register. This template is repeated infinitely so as to allow us to put the equation number in nicely. However, the final negative glue item won’t work properly, so the equation will look awful.

1114 \def\seq@dosplit#1#2{%
1115 \halign\bgroup%
1116  #1&&\mth@err@number\setbox\z@\hbox{##}\cr%
1117 #2\relax%

1118 }

\seq@eqnocr Here’s how we set equation numbers. Since the column provided raises errors as soon as a token finds its way into it, we start with a &\omit. Then we just put the equation number in a zero-width box. Finally, we reset the newline commands to avoid putting in more than one equation number, and do normal newline things.

1119 \def\seq@eqnocr{%
1120 &\omit%
1121 \hb@xt@\z@{\hss\eqa@eqpos\eqa@number}%
1122 \global\let\seq@docr\seq@cr%
1123 \global\let\seq@lastcr\seq@cr%
1124 \seq@cr%

1125 }

\seq@cr Newlines are very easy. We add a \jot of extra space, since this is a nice thing to do.

1126 \def\seq@cr{\tab@cr\seq@cr@i\interdisplaylinepenalty\@M}
1127 \def\seq@cr@i#1#2{\cr\noalign{\penalty#2\vskip\jot\vskip#1}}

3.7 Matrix handling

There’s been a complete and total overhaul of the spacing calculations for matrices here. The vertical spacing now bears an uncanny similarity to the rules TEX uses to space out \atop-like fractions, the difference being that you can have more than one column in a matrix. This has the interesting side-effect that we get an amsmath-style sub/superscript environment almost free of charge with the matrix handling (it just ends up being a script-size single-column matrix).

What is rather gratifying is that our matrix environment looks rather nicer than amsmath’s (which is based directly on array, giving it nasty restrictions on the numbers of columns and so on); in particular, the version here gives the ‘correct’ result for Knuth’s exercise 18.42 (which states categorically that a \smallskip should be placed between the rows of the big matrix).

Exercise 18.42 from The TEXbook

\begin{matrix}( & \begin{matrix}( \begin{matrix}a & b \\ c & d \end{matrix} ) & ( \begin{matrix}e & f \\ g & h \end{matrix} ) \\ 0 & ( \begin{matrix}i & j \\ k & l \end{matrix} ) \end{matrix} & ) \\ ( & \begin{matrix}(\begin{matrix}a & b \\ c & d \end{matrix}) & (\begin{matrix}e & f \\ g & h \end{matrix}) \\ 0 & (\begin{matrix}i & j \\ k & l \end{matrix}) \end{matrix} & ) \end{matrix}

                                         \def\mat##1
                                            0 &
                                            \end{#1}
                                         \]
                                       }

genmatrix The first job is to store my maths style and font away, because I’ll be needing it lots later.

1128 \def\genmatrix#1#2#3#4#5{%

\newcommand{\domatrix}[1]{

{\begin{#1}##1\end{#1}} \[ \begin{#1}

\mat{a & b \\ c & d} & \mat{e & f \\ g & h} \\[\smallskipamount] \mat{i & j \\ k & l} \domatrix{pmatrix} \domatrix{ams-pmatrix}

1129 \let\mat@style#1%
1130 \ifx#2\scriptstyle%
1131  \let\mat@font\scriptfont%
1132 \else\ifx#2\scriptscriptstyle%
1133  \let\mat@font\scriptscriptfont%
1134 \else%
1135  \let\mat@font\textfont%
1136 \fi\fi%

Now to cope with inserted text. This is easy.

1137 \ifx\mat@style\scriptstyle%
1138  \let\mat@textsize\scriptsize%
1139 \else\ifx\mat@style\scriptscriptstyle%
1140  \let\mat@textsize\scriptscriptsize%
1141 \else%
1142  \let\mat@textsize\relax%
1143 \fi\fi%

Now for some fun. I’ll remember how to start and end the matrix in a couple of macros \mat@left and \mat@right. I haven’t yet worked out exactly what needs to be in \mat@right yet, though, so I’ll build that up in a scratch token list while I’m making my mind up.

Initially, I want to open a group (to trap the style changes), set the maths style (to get the right spacing), insert the left delimiter, insert some spacing around the matrix, and start a centred box. The ending just closes all the groups and delimiters I opened.

1144 \def\mat@left{\bgroup\mat@style\left#4#3\vcenter\bgroup}%
1145 \toks@{\egroup#3\right#5\egroup}%

Now comes a slightly trickier bit. If the maths style is script or scriptscript, then I need to raise the box by a little bit to make it look really good. The right amount is somewhere around ^{3}/_{4} pt, I think, so that’s what I’ll use.

1146 \@tempswatrue%
1147 \ifx\mat@style\displaystyle\else\ifx\mat@style\textstyle\else%
1148  \@tempswafalse%
1149  \setbox\z@\hbox\bgroup$%
1150  \toks@\expandafter{\the\toks@$\m@th\egroup\raise.75\p@\box\z@}%
1151 \fi\fi%

If I’m not in maths mode right now, then I should enter maths mode, and remember to leave it later.

1152 \if@tempswa\ifmmode\else%
1153  $\m@th%
1154  \toks@\expandafter{\the\toks@$}%
1155 \fi\fi%

Now I’ve sorted out how to end the environment properly, so I can set up the macro, using \edef.

1156 \edef\mat@right{\the\toks@}%

Now see if there’s an optional argument. If not, create lots of centred columns.

1157 \@ifnextchar[\genmatrix@i{\genmatrix@i[[c]}%

1158 }

Now to sort out everything else.

1159 \def\genmatrix@i[#1]{%

Some initial setting up: choose the correct column set, and set up some variables for reading the preamble.

1160 \colset{matrix}%
1161 \tab@initread%

Now comes some of the tricky stuff. The space between columns should be 12 mu (by trial and error). We put the space in a box so we can measure it in the correct mathstyle.

1162 \setbox\z@\hbox{$\mat@style\mskip12mu$}%
1163 \edef\tab@tabtext{&\kern\the\wd\z@}%
1164 \tab@readpreamble{#1}%

Now we need to decide how to space out the rows. The code here is based on the information in appendix G of The TEXbook: I think it’d be nice if my matrices were spaced out in the same way as normal fractions (particularly \choosey things). The standard \baselineskip and \lineskip parameters come in really handy here.

The parameters vary according to the size of the text, so I need to see if we have scriptsize or less, or not. The tricky \if sorts this out.

1165 \if1\ifx\mat@style\scriptstyle1\else%
1166  \ifx\mat@style\scriptscriptstyle1\else0\fi\fi%
1167  \baselineskip\fontdimen10\mat@font\tw@%
1168  \advance\baselineskip\fontdimen12\mat@font\tw@%
1169  \lineskip\thr@@\fontdimen8\mat@font\thr@@%
1170 \else%
1171  \baselineskip\fontdimen8\mat@font\tw@%
1172  \advance\baselineskip\fontdimen11\mat@font\tw@%
1173  \lineskip7\fontdimen8\mat@font\thr@@%
1174 \fi%
1175 \lineskiplimit\lineskip%

Now actually set up for the alignment. Assign \\ to the correct value. Set up the \tabskip. Do the appropriate \mat@left thing set up above. And then start the alignment.

1176 \let\\\mat@cr%
1177 \tabskip\z@skip%
1178 \col@sep\z@%
1179 \mat@left%
1180 \halign\expandafter\bgroup\the\tab@preamble\tabskip\z@skip\cr%

Now for a little hack to make the spacing consistent between matrices of the same height. This comes directly from Plain TEX. This appears to make the spacing exactly the same as the TEX primites, oddly enough.

1181 \ifx\mat@font\textfont%
1182  \omit$\mat@style\mathstrut$\cr\noalign{\kern-\baselineskip}%
1183 \fi%

1184 }

Finishing the environment is really easy. We do the spacing hack again at the bottom, close the alignment and then tidy whatever we started in \mat@left.

1185 \def\endgenmatrix{%
1186 \crcr%
1187 \ifx\mat@font\textfont%
1188  \omit$\mat@style\mathstrut$\cr\noalign{\kern-\baselineskip}%
1189 \fi%
1190 \egroup%
1191 \mat@right%

1192 }

\mat@cr Newlines are really easy. The ∗-form means nothing here, so we ignore it.

1193 \def\mat@cr{\tab@cr\mat@cr@i{}{}}
1194 \def\mat@cr@i#1{\cr\noalign{\vskip#1}\@gobble}

\newmatrix This is how we define new matrix environments. It’s simple fun with \csname and

  \expandafter.
1195 \def\newmatrix#1#2{%
1196 \@namedef{#1}{\genmatrix#2}%
1197 \expandafter\let\csname end#1\endcsname\endgenmatrix%

1198 }

matrix pmatrix dmatrix smatrix spmatrix sdmatrix smatrix* spmatrix* sdmatrix* Now we define all the other environments we promised. This is easy.
1199 \newmatrix{matrix}{{\textstyle}{\textstyle}{\,}{.}{.}}
1200 \newmatrix{pmatrix}{{\textstyle}{\textstyle}{\,}{(}{)}}
1201 \newmatrix{dmatrix}{{\textstyle}{\textstyle}{\,}}
1202 \newmatrix{smatrix}{{\scriptstyle}{\scriptstyle}{}{.}{.}}
1203 \newmatrix{spmatrix}{{\scriptstyle}{\scriptstyle}{}{(}{)}}
1204 \newmatrix{sdmatrix}{{\scriptstyle}{\scriptstyle}{}}
1205 \newmatrix{smatrix*}{{\scriptstyle}{\textstyle}{}{.}{.}}
1206 \newmatrix{spmatrix*}{{\scriptstyle}{\textstyle}{}{(}{)}}
1207 \newmatrix{sdmatrix*}{{\scriptstyle}{\textstyle}{}}

script Now for superscripts and subscripts. This is fairly easy, because I took so much care over the matrix handling.

1208 \def\script{%
1209 \let\mat@style\scriptstyle%
1210 \def\mat@left{\vcenter\bgroup}%
1211 \def\mat@right{\egroup}%
1212 \let\mat@font\scriptfont%
1213 \let\mat@textsize\scriptsize%
1214 \@ifnextchar[\genmatrix@i{\genmatrix@i[c]}%

1215 }

1216 \let\endscript\endgenmatrix

Now define the column types.

1217 \colpush{matrix}
1218 \coldef l{\tabcoltype{\kern\z@$\mat@style}{\m@th$\hfil}}
1219 \coldef c{\tabcoltype{\hfil$\mat@style}{\m@th$\hfil}}
1220 \coldef r{\tabcoltype{\hfil$\mat@style}{\m@th$}}
1221 \coldef T#1{\tab@aligncol{#1}{\begingroup\mat@textsize}{\endgroup}}

The repeating type is more awkward. Things will go wrong if this is given before the first column, so we must do a whole repeat by hand. We can tell if we haven’t contributed a column yet, since \tab@column will be zero. Otherwise, we fiddle the parser state to start a new column, and insert the & character to make TEX repeat the preamble.

1222 \coldef {[}{%
1223 \@firstoftwo{%
1224  \ifnum\tab@columns=\z@%
1225  \def\@tempa##1\q@delim{%
1226  \tab@mkpreamble##1[##1\q@delim%

1227 }%

1228  \expandafter\@tempa%
1229  \else%
1230  \tab@setstate\tab@prestate%
1231  \tab@append\tab@preamble{&}%
1232  \expandafter\tab@mkpreamble%
1233  \fi%

1234 }% 1235 }

We’re done defining columns now.

1236 \colpop

3.8 Dots…

Nothing whatsoever to do with alignments, although vertical and diagonal dots in small matrices look really silly. The following hacky definitions work rather better.

\mdw@dots First of all, here’s some definitions common to both of the dots macros. The macro takes as an argument the actual code to draw the dots, passing it the scaled size of a point in the scratch register \dimen@; the register \box 0 is set to contain a dot of the appropriate size.

1237 \def\mdw@dots#1{\ensuremath{\mathpalette\mdw@dots@i{#1}}}
1238 \def\mdw@dots@i#1#2{%
1239 \setbox\z@\hbox{$#1\mskip1.8mu$}%
1240 \dimen@\wd\z@%
1241 \setbox\z@\hbox{$#1.$}%

1242 #2% 1243 }

\vdots I’ll start with the easy one. This is a simple translation of the original implementation.

1244 \def\vdots{%
1245 \mdw@dots{\vbox{%
1246  \baselineskip4\dimen@%
1247  \lineskiplimit\z@%
1248  \kern6\dimen@%
1249  \copy\z@\copy\z@\box\z@%

1250 }}% 1251 }

\ddots And I’ll end with the other easy one…

1252 \def\ddots{%
1253 \mdw@dots{\mathinner{%

1254  \mkern1mu%
1255  \raise7\dimen@\vbox{\kern7\dimen@\copy\z@}%
1256  \mkern2mu%
1257  \raise4\dimen@\copy\z@%
1258  \mkern2mu%
1259  \raise\dimen@\box\z@%
1260  \mkern1mu%

1261 }}% 1262 }

3.9 Lucky dip

Time to round off with some trivial environments, just to show how easy this stuff is.

cases smcases These are totally and utterly trivial.

1263 \def\cases{\left\{\,\array{@{}lTl@{}}}
1264 \def\endcases{\endarray\,\right.}
1265 \def\smcases{\left\{\smarray{@{}lTl@{}}}
1266 \def\endsmcases{\endsmarray\,\right.}

3.10 Error messages

Some token saving:

1267 \def\mth@error{\PackageError{mathenv}}

Now for the error messages.

1268 \def\mth@err@number{%
1269 \mth@error{Too many `&' characters found}{%
1270  You've put too many `&' characters in an alignment^^J%
1271  environment (like `eqnarray' or `spliteqn') and wandered^^J%
1272  into trouble.  I've gobbled the contents of that column^^J%
1273  and hopefully I can recover fairly easily.%

1274 }% 1275 }

1276 \def\mth@err@mdsp{%
1277 \mth@error{Can't do displays in nondisplay maths mode}{%
1278  You're trying to start a display environment, but you're^^J%
1279  in nondisplay maths mode.  The display will appear but^^J%
1280  don't blame me when it looks horrible.%

1281 }% 1282 }

1283 \def\mth@err@hdsp{%
1284 \mth@error{Can't do displays in LR mode}{%
1285  You're trying to start a display environment, but you're^^J%
1286  in LR (restricted horizontal) mode.  Everything will go^^J%
1287  totally wrong, so your best bet is to type `X', fix the^^J%
1288  mistake and start again.%

1289 }% 1290 }

That’s all there is. Byebye.

1291 ⟨/mathenv⟩

Mark Wooding, 16 May 1996

Appendix

A The GNU General Public Licence

The following is the text of the GNU General Public Licence, under the terms of which this software is distrubuted.

GNU GENERAL PUBLIC LICENSE Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

A.1 Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software—to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation’s software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author’s protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors’ reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone’s free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

A.2 Terms and conditions for copying, distribution and modification

0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The “Program”, below, refers to any such program or work, and a “work based on the Program” means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term “modification”.) Each licensee is addressed as “you”.

Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

1. You may copy and distribute verbatim copies of the Program’s source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

(a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.

(b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.

(c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:

(a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

(b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

(c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients’ exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and “any later version”, you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY

11. Because the Program is licensed free of charge, there is no warranty for the Program, to the extent permitted by applicable law. except when otherwise stated in writing the copyright holders and/or other parties provide the program “as is” without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the Program is with you. Should the Program prove defective, you assume the cost of all necessary servicing, repair or correction.

12. In no event unless required by applicable law or agreed to in writing will any copyright holder, or any other party who may modify and/or redistribute the program as permitted above, be liable to you for damages, including any general, special, incidental or consequential damages arising out of the use or inability to use the program (including but not limited to loss of data or data being rendered inaccurate or losses sustained by you or third parties or a failure of the Program to operate with any other programs), even if such holder or other party has been advised of the possibility of such damages.

END OF TERMS AND CONDITIONS

A.3 Appendix: How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.

<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy  <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it

starts in an interactive mode:

Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.

The hypothetical commands ‘show w’ and ‘show c’ should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than ‘show w’ and ‘show c’; they could even be mouse-clicks or menu items–whatever suits your program.

You should also get your employer (if you work as a programmer) or your school, if any, to sign a “copyright disclaimer” for the program, if necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice

This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.

Index

The italic numbers denote the pages where the corresponding entry is described, numbers underlined point to the definition, all others indicate the places where it is used.

Symbols

\, . . . . . . 1199–1201, 1263, 1264, 1266 \@@ . . . . . . . . . . . . . . . . . . . . 822, 824 \@M . . . . . . . . . . . . . . . . 527, 998, 1126 \@array . . . . . . . . . . . . . . . . . 426, \underline{428} \@arraycr . . . . . . . . . . . . . . . . . . . \underline{785} \@arrayleft 421, 424, 460, 473, 486, 504

\@arrayparboxrestore . . . . . . . . . . 314

\@arrayright . . 422, 425, 464, 477, 490 \@arstrut . . . . . . . . . . . . . . . 342, 437 \@arstrutbox . . . . . . . . . . . . . 325,

328, 331, 332, 409, 482, 541, 797 \@centering . . . . . . . . . . . . . . 906, 914 \@currentlabel . . . . . . . . . . 954, 1089 \@depth . . . . . . . . . . . . . . . . . 412, 797 \@endpetrue . . . . . . . . . . . . . . . . . . 509 \@eqalastfalse . . . . . . . . . . . . . . . 958 \@eqalasttrue . . . . . . . . . . . . . . . 1011 \@eqncr . . . . . . . . . . . . . . . . . 968, 998 \@firstofone . . . . . . . . . . . . . . . . . 795 \@firstoftwo . . . . . . . . . . . . 621, 1223 \@fleqntrue . . . . . . . . . . . . . . . . . . 894 \@gobble …. 172, 703, 805, 1061, 1194 \@height . . . . . . . . . 411, 644, 675, 710 \@ifnextchar … 240, 247, 249, 254,

262, 263, 426, 585, 588, 593, 596, 694, 742, 750, 761, 767, 814, 951, 1034, 1036, 1157, 1214

\@ifstar . . . . . . . . . . . . . . . . . . . . 811 \@ifundefined . . . . . . . . 227, 234, 720 \@ignoretrue . . . . . . . . . . . . . 509, 980 \@leqnotrue . . . . . . . . . . . . . . . . . . 895 \@let@token . . . . . . . . . . . . . . . . . .

. 205, 207, 214, 215, 217, 633, 636 \@m . . . . . . . . . . . . . . . . . . . . . . . . 907 \@maybe@unskip . \underline{29}, 185, 344, 436, 795 \@minipagefalse . . . . . . . . . . . . . . 318 \@minipagetrue . . . . . . . . . . . . . . . 316 \@minus . . . . . . . . . . . . . . . . . 912, 915 \@mkpream . . . . . . . . . . . . . . . . . . . 338 \@namedef . . . . . . . . . . . . . . . . 89,

94, 101, 233, 727, 950, 1074, 1196 \@nameuse . . . . . . . . . . . . . . . 230, 237 \@parboxrestore . . . . . . . . . . . . . . 554 \@plus . . . . . . . . . . . . . . 515, 516, 907 \@preamble . . . . . . . . . . . . . . . . . . 346 \@secondoftwo . . . . . . . . . . . . . . . . 623 \@sharp . . . . . . . . . . . . . . . . . . . . . 344 \@sptoken . . . . . . . . . . . . . . . . . . . 207 \@tabarray . . . . . . . . . . 364, 402, 423 \@tabular . . . . . . . . . . . \underline{394}, 404, 405 \@tempa . . . . . . . . . . . . . . . . . . 64,

65, 83, 87, 108, 110, 112, 215, 219, 221, 224, 731, 733, 737, 754, 756, 758, 822, 824, 1225, 1228

\@tempcnta . . . . . . . . . . . . . . 590, 591 \@tempswafalse . . . . . . . . . . . . . . .

…. 468, 501, 503, 505, 617, 1148 \@tempswatrue …. 496–498, 627, 1146

                                       \@totalleftmargin . . . . 510, 513, 557

\@whilenum . . . . . . . . . . . . . . 664, 671 \@width . . . . . . 289, 413, 648, 710, 797 \\ …. 807, 968, 1039, 1098, 1106, 1176 \{ . . . . . . . . . . . . . . . . . . . 1263, 1265

A

\abovedisplayskip . . . . . . . . . . . . 944
\afterassignment . . . . . 585, 590, 601

\array . . . . . . . . . . . . . . . . . 370, 1263 array (environment) . . . . . . . . . . 5, \underline{370} \arraycolsep . . . . . . . . . . . . . . . . . 371 \arrayextrasep . . . . . . . . . 11, 24, 372

\arrayrulewidth . . . 289, 644, 645,

648, 650, 655, 660, 669, 675, 739 \arraystretch . . . . . . . . . . . . 411, 412

\AtBeginDocument . . . . . . . . . . . . . 905

B

\baselineskip . . . . . . . . 448, 1167,

1168, 1171, 1172, 1182, 1188, 1246

\belowdisplayskip . . . . . . . . . . . . 942

C

\c@equation . . . . . . . . . 981, 1032, 1084 \cases . . . . . . . . . . . . . . . . . . . . . 1263 cases (environment) . . . . . . . . 25, \underline{1263} \chardef . . . . . . . . . . . . . . . . . . 31–39 \cline . . . . . . . . . . . . . . . . 7, \underline{649}, 775 \cline@i . . . . . . . . . . . . . . . . 653, 657 \col@sep . . . . . . . . . . . . . . . 15, 91,

102, 371, 384, 399, 966, 1044, 1178 \coldef . . . . . . . . . . . 13, \underline{240}, 279–

283, 289–292, 295, 298, 301– 304, 985–988, 990–992, 1218–1222

\coldef@i . . . . . . . . . . . . . . . 240, 241 \coldef@ii . . . . . . . . . . . . . . 241, 242 \collet . . . . . . . 14, \underline{247}, 989, 993–996 \collet@i . . . . . . . . . . . . . . . 247, 248 \collet@ii . . . . . . . . . . 250, 251, 253 \collet@iii . . . . . . . . . . 255, 256, 258 \colpop . . . . . . . . . . 12, \underline{175}, 997, 1236 \colpush . . . . . . . . . 12, \underline{175}, 984, 1217 \colset . . . . . . . . . . . . . . . . . . 11,

\underline{175}, 278, 340, 434, 960, 1040, 1160 \count@ . . . . . . . 305, 306, 308, 585,

588, 591, 601, 618, 627, 681, 683 \cr … 455, 646, 654, 677, 713, 800,

973, 1026, 1053, 1061, 1111, 1116, 1127, 1180, 1182, 1188, 1194

\crcr . . . . . . . . . . . . . . 377, 1056, 1186

D

\ddots . . . . . . . . . . . . . . . . . . 24, \underline{1252} \DeclareOption . . . . . . . . . . . 894, 895 \dimen@ … 389, 411, 417, 518–521,

523, 524, 788–790, 797, 799, 1240, 1246, 1248, 1255, 1257, 1259

\displaystyle . . . . . . . 946, 1101, 1147 \displaywidth . . . . . . . 971, 1005, 1100 dmatrix (environment) . . . . . . 23, \underline{1199} \do . . . . . . . . . . . . . . . . . . . . 664, 671 \doafter . . . . . . . . . . . . . . . . . . . . 559 \doublerulesep . . . . . . . . . . . . . . .

. . . . . 122, 124, 637, 638, 761, 767 \dsp@end . . . . . . . . 921, 926, 978, 1082 \dsp@endinner . . . . . . . . . . . . 926, \underline{930} \dsp@endouter . . . . . . . . . . . . 921, \underline{924} \dsp@start . . . . . . . . . . \underline{931}, 969, 1096 \dsp@startinner . . . . . . . . . . \underline{925}, 934 \dsp@startouter . . . . . . . . . . \underline{920}, 937 \dsp@tabpause . . . . . . . . \underline{940}, 965, 1097

E \end . . . . . . . . . . . . . . . . . . . . 105, 106 \endarray …. 376, 392, 406, 407, 1264 \endcases . . . . . . . . . . . . . . . . . . 1264 \endeqlines . . . . . . . . . . . . . . . . . 1063 \endeqnalign . . . . . . . . . . . . . . . . 1055 \endeqnarray . . . . . . . . . 975, 983, 1063 \endgenmatrix . . . . . . 1185, 1197, 1216 \endscript . . . . . . . . . . . . . . . . . 1216 \endsmarray . . . . . . . . . . . . . 392, 1266 \endsmcases . . . . . . . . . . . . . . . . . 1266 \endspliteqn . . . . . . . . . . . 1079, 1086 \endsubsplit . . . . . . . . . . . . . . . . 1110 \endtabular . . . . . . . . . . . . . . . . . . 406 \ensuremath . . . . . . . . . . . . . . . . . 1237 environments:

array . . . . . . . . . . . . . . . . . . 5, \underline{39} cases . . . . . . . . . . . . . . . . . . 25, \underline{73} dmatrix . . . . . . . . . . . . . . . . 23, \underline{71} eqlines* . . . . . . . . . . . . . . . 19, \underline{65} eqlines . . . . . . . . . . . . . . . . 19, \underline{65} eqnalign . . . . . . . . . . . . . . . 19, \underline{64} eqnarray* . . . . . . . . . . . . . . . 16, \underline{60} eqnarray . . . . . . . . . . . . . . . 16, \underline{60} genmatrix . . . . . . . . . . . . . . . 24, \underline{68} matrix . . . . . . . . . . . . . . . . . 23, \underline{71} newmatrix . . . . . . . . . . . . . . . . . 25 pmatrix* . . . . . . . . . . . . . . . . . 24 pmatrix . . . . . . . . . . . . . . . . 23, \underline{71} script . . . . . . . . . . . . . . . . . 25, \underline{71} sdmatrix* . . . . . . . . . . . . . . . 24, \underline{71} sdmatrix . . . . . . . . . . . . . . . 24, \underline{71} smarray . . . . . . . . . . . . . . . . . . \underline{39} smatrix* . . . . . . . . . . . . . . . . . \underline{71} smatrix . . . . . . . . . . . . . . . . 24, \underline{71} smcases . . . . . . . . . . . . . . . . 25, \underline{73} spliteqn* . . . . . . . . . . . . . . . 21, \underline{65} spliteqn . . . . . . . . . . . . . . . 21, \underline{65} spmatrix* . . . . . . . . . . . . . . . 24, \underline{71} spmatrix . . . . . . . . . . . . . . . 24, \underline{71} subsplit . . . . . . . . . . . . . . . 21, \underline{67} tabular* . . . . . . . . . . . . . . . 5, \underline{40} tabular . . . . . . . . . . . . . . . . 5, \underline{40}

\eqa@@lastcol . . . . . . 1025, 1029, 1030 \eqa@defnumber . . . . . . . 956, 957, 1024 \eqa@eqcount … 948, 1031, \underline{1032}, 1071 \eqa@eqnum . . . . . . . . . 976, 1000, \underline{1010} \eqa@eqnum@i . . . . . . . 1012, 1014, 1028 \eqa@eqnum@ii . . . . . . . . . . 1017, 1022 \eqa@eqnum@iii . . . . . . . . . 1019, 1028 \eqa@eqpos . . . . . . . . \underline{1003}, 1023, 1121 \eqa@lastcol … 973, 1011, 1025, \underline{1029} \eqa@number . . . . . . . . . . . 955, 957,

979, 1023, 1024, 1031, 1033, 1070, 1071, 1075, 1076, 1083, 1121

\eqa@oldnumber . . . . . . . . . . . . . . .

. . . . . . 955, 979, 1070, 1075, 1083 \eqacloseskip . . . . . . . . 900, 906, 964 \eqacolskip . . . . . . . . . . 901, 907, 991 \eqacr@i . . . . . . . . . . . . . . . . 998, 999 \eqainskip . . . . . . 902, 908, 966, 1044 \eqaopenskip . . . . . . 899, 910, 914, 970 \eqastyle . . . . . . . . 946, 985–988, 990 \eqlines . . . . . . . . . . . . . . . . . . . 1062 eqlines (environment) . . . . . . 19, \underline{1062} eqlines* (environment) . . . . . 19, \underline{1064} \eqn@cr . . . . . . . . . . . . . . . 1039, \underline{1060} \eqn@cr@i . . . . . . . . . . . . . 1060, 1061 \eqnalign . . . . . . . . . . . . . . . . . . 1034 eqnalign (environment) . . . . . 19, \underline{1034} \eqnalign@i . . . . . . . . . . . . 1034, 1035 \eqnalign@ii . . . . . . . . . . . 1036, 1038 \eqnarray . . . . . . . . . . . . . . 947, 1062 eqnarray (environment) . . . . . . 16, \underline{947} eqnarray* (environment) . . . . . 16, \underline{947} \eqnarray@i . . . . . . . . . . 948, 950, 951 \eqnarray@ii . . . . . . . . . . . . . 951, 952 \eqnumber . . . . . . . . . . . . . . . 19, \underline{1031} \everycr . . . . . . . . . . . . 453, 967, 1046 \everypar . . . . . . . . . . . . . . . 317, 319 \extracolsep . . . . . . . . . . . . . 105, 106 \extrarowheight . . . . . . . . 9, 383, 417

F

\fontdimen …. 1167–1169, 1171–1173 \futurelet . . . . . . . . . . . . . . 205, 633

G

\genmatrix . . . . . . . . . . . . 1128, 1196 genmatrix (environment) …. 24, \underline{1128} \genmatrix@i . . . . . . . 1157, 1159, 1214

H

\halign …. 454, 971, 1053, 1115, 1180 \hb@xt@ 688, 973, 990, 1005, 1100, 1121 \hfilneg . . . . . . 1080, 1103, 1108, 1111 \hline . . . . . . . . . . . . . . \underline{629}, 731, 733 \hline@i . . . . . . . . . . . . . . . . 633, 635 \hlx . . . . . . . . . . . . . . . . . . 9, \underline{717}, 779 \hlx␣. . . . . . . . . . . . . . . . . . . . . . . \underline{776} \hlx␣/ . . . . . . . . . . . . . . . . . . . . . . \underline{740} \hlx␣b . . . . . . . . . . . . . . . . . . . . . . \underline{739} \hlx␣c . . . . . . . . . . . . . . . . . . . . . . \underline{775} \hlx␣h . . . . . . . . . . . . . . . . . . . . . . \underline{728} \hlx␣s . . . . . . . . . . . . . . . . . . . . . . \underline{764} \hlx␣v . . . . . . . . . . . . . . . . . . . . . . \underline{745}

\hlx@cmd@break@i . . . . . . . . . 742, 744

\hlx@loop . . . . . . . . 717, 718, 721,

731, 733, 739, 744, 749, 773, 775 \hlx@space@i . . . . . . . . . . . . . 767, 769 \hlx@vgap@i . . . . . . . . . . . . . . 750, 752 \hlx@vgap@ii . . . . . . 750, 754, 756, 760 \hlx@vgap@iii . . . . . . . . . . . . 761, 763 \hlxdef . . . . . . . . . . . . . . . . . \underline{727},

728, 739, 740, 745, 764, 775, 776 \hrule . . . . . . . . . . . . . . . . . . 644, 675

I

\if@eqalast . . . . . . . . . . . . . 898, 1016 \if@fleqn . . . . . . . . 892, 909, 927, 988 \if@leqno . . . . . . 893, 928, 1003, 1090 \if@tempswa 500, 503, 504, 507, 620, 1152 \ifinner . . . . . . . . . . . . 501, 933, 936 \ifinrange . . . . . . . . . . . . . . \underline{616}, 682 \ifinrange@i . . . . . . . . . . . . . 619, 626 \iftab@firstcol . . . . . . . . . . . . 19, 45 \iftab@initrule . . . . . 20, 74, 116, 200 \iftab@rule . . . . . . . . . . . . . . . . 21, 90 \iftab@vgap . . . . . . . . . . . . . . . . . . 22 \ignorespaces . 185, 344, 436, 577, 783

\interdisplaylinepenalty . . 998, 1126

J

\jot . . . . . . . . . . . 24, 1001, 1061, 1127

L

\lastskip . . . . . . . . . . . . . . . . . . . 29 \leaders . . . . . . . . . . . . . . . . 644, 675 \leavevmode . . . . . . . . . . 460, 473, 486 \left . . . . . . . . . . . . . 1144, 1263, 1265 \lineskip . . . . . 448, 1169, 1173, 1175 \lineskiplimit . . . . . . . . . 1175, 1247 \linewidth . . . . . . . . . . 512, 518, 557 \loop . . . . . . . . . . . . . . . . . . . . . . . 306 \lower . . . . . . . . . . . . . . . . . . . . . . 550 \m@th . . . . . . . . . 369, 449, 464, 477,

490, 959, 1150, 1153, 1218–1220 smarray . . . . . . . . . . . . . . . . . . . . . . 7 \mat@cr . . . . . . . . . . . . . . . 1176, \underline{1193} \mat@cr@i . . . . . . . . . . . . . 1193, 1194 \mat@font . 1131, 1133, 1135, 1167–

1169, 1171–1173, 1181, 1187, 1212 \mat@left . . . . . . . . . 1144, 1179, 1210 \mat@right . . . . . . . . 1156, 1191, 1211 \mat@style . . . . . . . . . 1129, 1137,

1139, 1144, 1147, 1162, 1165, 1166, 1182, 1188, 1209, 1218–1220

\mat@textsize . . . . . . . . . . . . . . . .

…. 1138, 1140, 1142, 1213, 1221 \mathindent . . . . . . . . . . . 910–912, 927 \mathinner . . . . . . . . . . . . . . . . . 1253 \mathpalette . . . . . . . . . . . . . . . . 1237 \mathstrut . . . . . . . . . . . . 1182, 1188 matrix (environment) . . . . . . . 23, \underline{1199} \mdw@dots . . . . . . . . . \underline{1237}, 1245, 1253 \mdw@dots@i . . . . . . . . . . . . 1237, 1238 \medskip . . . . . . . . . . . . 190, 191, 508 \MessageBreak . . . . . . . . . . . . . . . . 780 \mkern . . . . . . . . 1254, 1256, 1258, 1260 \mskip . . . . . . . . . . . . . . . . 1162, 1239 \mth@err@hdsp . . . . . . . . . . . 936, 1283 \mth@err@mdsp . . . . . . . . . . . 933, 1276 \mth@err@number …. 1029, 1116, 1268 \mth@error …. 1267, 1269, 1277, 1284 \multicolumn …. 20, 23, \underline{566}, 875, 876 \multispan . . . . . . . . . . . . . . 567, 643

N

\NC@find . . . . . . . . . . . . . . . . 275, 277 \nct@i . . . . . . . . . . . . . . . . . . 262, 263 \nct@ii . . . . . . . . . . . . . . . . . 263, 264 \nct@iii . . . . . . . . . . . . . 263–265, 273 \newcol@ . . . . . . . . . . . . . . . . . . . . 273 \newcolumntype . . . . . . . . . . . . 11, \underline{262} \newcommand . . . . . . . . . . . . . 267, 1031 \newcount . . . . . . . . . . . . . . . . . . 2, 3 \newdimen . . . . . . . . . . . . . . . . . . 9–16 \newif . . . . . . . . . 19–22, 892, 893, 898 \newmatrix . . . . . . . . \underline{1195}, 1199–1207 newmatrix (environment) . . . . . . . . 25 \newskip . . . . . . . . . . . 17, 18, 899–904 \newtoks . . . . . . . . . . . . . . . . . . . 4, 5 \noalign . . . . . . . . . . . . . . . . 553,

631, 650, 655, 660, 690, 714, 729, 739, 741, 746, 765, 801, 1001, 1061, 1127, 1182, 1188, 1194

\nobreak . . . . . 562, 690, 714, 746, 766 \noindent . . . . . . . . . . . . . . . . . . . 558

                                       \nointerlineskip . . . . . . . . . 327, 793

\nonumber . . . . . . . . . . . . . . . . . . \underline{1033} \normalcolor . . . . . . . . . . . 1005, 1008 \normalfont . . . . . . . . . . . . 1005, 1008 \number . . . . . . . . . . . . . . . . . . . . . 85

O \omit . . . . . . . . . . . . . 79, 651, 661,

678, 679, 691, 747, 1120, 1182, 1188

P \p@equation . . . . . . . . . . . . . 954, 1089 \PackageError . . . . . . . . . . . 826, 1267 \PackageWarning . . . . . . . . . . . . . . 778 \pagebreak . . . . . . . . . . . . . . . . . . 744 \par . . . . . . . . . . . . . 323, 452, 501, 509 \parshape . . . . . . . . . . . . . . . . . . . 557 \parskip . . . . . . . . . . . . . . . . 508, 556 \penalty . . . . . . . . . . . . . . . . . . . .

190, 191, 527, 942, 944, 1001, 1127 pmatrix (environment) . . . . . . 23, \underline{1199} pmatrix* (environment) . . . . . . . . . 24

                                       \postdisplaypenalty . . . . . . . 190, 942
                                       \predisplaypenalty . . . . . . . . 191, 944

\prevdepth . . . . . . . . . . 325, 326, 792 \ProcessOptions . . . . . . . . . . . . . . 896 \protect . . . . . . . . . . . . . . . . 779, 875 \providecommand . . . . . . . . . . . . . . 393

Q \q@delim … \underline{30}, 204, 214, 582, 585,

596, 615, 717, 719, 776, 1225, 1226 \qquad . . . . . . . . . . . . . . . . . . . . . 1108 \quad . . . . . . . . . . . . . . . . . . . . . . . 992

R \raise …. 546, 1150, 1255, 1257, 1259 \ranges . . . . . . . . . . . . . \underline{579}, 619, 653 \ranges@do . . . . . . . . . . 588, 591, 602 \ranges@done . . . . . . . . . 585, 597, 615 \ranges@i . . . . . . . . . . . . . . . 582, 584 \ranges@ii . . . . . . . . . . 585, 587, 601 \ranges@iii . . . . . . . . . . . . . . 588, 590 \ranges@iv . . . . . . . . . . . . . . 590, 591 \ranges@temp . . . . . . . . . . . . . 580, 605 \ranges@v . . . . . . . . . . . 588, 591, 592 \ranges@vi . . . . . . . . . . 594, 598, 601 \renewcommand . . . . . . . . . . . . . . . . 648 \repeat . . . . . . . . . . . . . . . . . . . . . 309 \RequirePackage . . . . . . . . . . . . . . 897 \right . . . . . . . . . . . . 1145, 1264, 1266 \rlap . . . . . . . . . . . . . . . . . . . . . . 1005

S \savenotes 293, 296, 299, 459, 472, 485 \script . . . . . . . . . . . . . . . . . . . . 1208 script (environment) . . . . . . . 25, \underline{1208} \scriptfont . . . . . . . . . . . . 1131, 1212

\scriptscriptfont . . . . . . . . . . . 1133
\scriptscriptsize . . . . . . . . . . . 1140
\scriptscriptstyle . . 1132, 1139, 1166

\scriptsize . . . . . 387, 388, 1138, 1213 \scriptstyle . . . . . . . . . . . . . 386,

1130, 1137, 1165, 1202–1207, 1209 sdmatrix (environment) . . . . . 24, \underline{1199} sdmatrix* (environment) …. 24, \underline{1199} \seq@cr . . 1093, 1106, 1122–1124, \underline{1126} \seq@cr@i . . . . . . . . . . . . . 1126, 1127 \seq@docr . . . . . 1091, 1093, 1098, 1122 \seq@dosplit . . . . . . . 1099, 1108, \underline{1114} \seq@eqnocr . . . . . . . . 1091, 1095, \underline{1119} \seq@lastcr . . . . . . . . 1080, 1095, 1123 \show . . . . . . . . . . . . . . . . . . . . . . . 358 \showcol . . . . . . . . . . . . . . . . . . . . 357 \showpream . . . . . . . . . . . . . . . . . . 351 \showthe . . . . . . . . . . . . . . . . 354, 355 \smarray . . . . . . . . . . . . . . . 382, 1265 smarray (environment) . . . . . . . . . . \underline{382} \smarraycolsep . . . . . . . . . 12, 25, 384

\smarrayextrasep . . . . . . . 13, 26, 385

smatrix (environment) . . . . . . 24, \underline{1199} smatrix* (environment) . . . . . . . . \underline{1199} \smcases . . . . . . . . . . . . . . . . . . . 1265 smcases (environment) . . . . . . 25, \underline{1263} \span . . . . . . . . . . . . . . . . . . . . . . . 679 \spewnotes 294, 297, 300, 465, 479, 492 \spliteqn . . . . . . . . . . . . . . . . . . 1069 spliteqn (environment) . . . . . 21, \underline{1069} spliteqn* (environment) …. 21, \underline{1069} \spliteqn@i . . . . . . . . 1072, 1077, \underline{1087} \splitleft … 903, 911, 915, 916, 1101 \splitright . . . . . . 904, 912, 916, 1102 spmatrix (environment) . . . . . 24, \underline{1199} spmatrix* (environment) …. 24, \underline{1199} \stepcounter . . . . . . . . . . . . 953, 1088 \strut . . . . . . . . . . . . . . . . . . . . . . 388 \strutbox . . . . . . . . . . . 412, 417, 418 \subsplit . . . . . . . . . . . . . . . . . . 1105 subsplit (environment) . . . . . 21, \underline{1105}

T

\tab@@cr . . . . . . . . . . . . . . . . 659, 677 \tab@@magic@@ . . . . . . . . . . . . 275, 277 \tab@@span@omit . . . . . . . . . . 672, 679 \tab@@tab@omit . . . . . . . . . . . 665, 678

\tab@addruleheight . . . . . . . . . . . .

. . . . . . . . \underline{532}, 638, 645, 712, 771 \tab@aligncol . . . . . . . . 282–284, 1221 \tab@append . . . . . . . . . . . . . . . . . .

40, 46, 79, 91, 96–98, 102, 122, 123, 130, 131, 148, 156–158, 1231

\tab@array . . . . . . . . . . \underline{360}, 374, 390 \tab@arraycr . . . . . . . . . 450, 785, 786 \tab@bgroup . 96, 279–281, 347, 362, 397 \tab@bmaths . . . . . . . 283, 362, \underline{366}, 386 \tab@bpar . . . . . . . . 293, 296, 299, \underline{311} \tab@btext . . . . . . . 282, \underline{366}, 387, 397 \tab@checkrule . . . . . . . . . . . \underline{680}, 699

                                       \tab@checkrule@i . . . . . . . . . 685, 688

\tab@ckr . . . . . . . . . 125, 132, 699, 703 \tab@colset \underline{179}, 227, 230, 234, 237,

240, 247, 256, 266, 267, 358, 844 \tab@colstack . . . . . . . . . 175, 177–179 \tab@colstate . . . . . . . . . 36, 151, 155 \tab@columns . . . . . . . . . . . . . . . 3,

99, 125, 132, 154, 192, 643, 1224 \tab@commit . . . . . . . . . . . . 44, 78, 202 \tab@cr . . . . . . . . . . . . . . . . . . . . .

15, 786, \underline{808}, 998, 1060, 1126, 1193 \tab@cr@i . . . . . . . . . . . . . . . 811, 813 \tab@cr@ii . . . . . . . . . . . . . . 814, 816 \tab@deepmagic . . . . . . . . . . . 268, 274 \tab@dohline . . . . . . . . . . . . . 630, \underline{642} \tab@doreadpream … 14, 199, \underline{204}, 307 \tab@egroup . 97, 279–281, 348, 363, 398 \tab@emaths . . . . . . . . . . 283, 363, \underline{366} \tab@endheight . . . . . 16, 430, 467,

482, 534, 541, 545, 546, 549, 550 \tab@endpause . . . . . . . . 191, 563, 943 \tab@epar . . . . . . . . 294, 297, 300, \underline{322}

                                       \tab@err@misscol . . . . . . . . . . 95, 827

\tab@err@multi . . . . . . . . . . . 573, 874

                                       \tab@err@oddgroup . . . . . . . . 218, 835

\tab@err@range . . . . . . . . . . . 598, 882 \tab@err@unbext . . . . . . . . . . 505, 866 \tab@err@unbmm . . . . . . . . . . . 503, 858 \tab@err@unbrh . . . . . . . . . . . 501, 850 \tab@err@undef . . . . . . . . . . . 228, 843 \tab@error . . . . . . . . . . . 826, 828,

836, 844, 851, 859, 867, 875, 883 \tab@etext . . . . . . . . . . 282, \underline{366}, 398 \tab@extracol . . . . . . . . \underline{105}, 114, 138 \tab@extracol@i . . . . . . . . . . 105, 106 \tab@extrasep . . 27, 372, 385, 400, 789

                                       \tab@firstcolfalse . . . . . . . . . . . . 57
                                       \tab@firstcoltrue . . . . . . . . . . . . 196

\tab@halign . . . . . . . . . . 444, 446, 454 \tab@head . . . . . . . . . . . . . . . \underline{168}, 179 \tab@head@i . . . . . . . . . . . . . . 173, 174 \tab@hlstate . . . . . . . . . . . . . 431,

433, 481, 494, 531, 533, 538–540 \tab@initread . . . . . . . . . . 15, \underline{180},

341, 352, 435, 570, 961, 1041, 1161

\tab@initrulefalse . . . . . . . . . . . . 75
\tab@initruletrue . . . . . . . . . . . . 195

\tab@left …. 454, 458, 471, 484, 508 \tab@leftskip . . . . . . . . . . . . . . . .

. . 17, 440, 453, 510, 515, 521, 523 \tab@limitstate . . . . . . . . . . . . 39, 70 \tab@looped . . . . . . . . . . . 77, 193, 573 \tab@loopstate . . . . . . . . . . . . . 32, 71 \tab@lowerbase . . . . . . . . . . . 491, \underline{548} \tab@midtext … 49, 185, 344, 436, 572

\tab@mkpreamble . . . . . . . . . . . . . .

… 204, \underline{205}, 244, 269, 1226, 1232

\tab@mkpreamble@i . . . . . . . . 205, 206
\tab@mkpreamble@ii . . . . . . . . 210, 213
\tab@mkpreamble@iii . . . . . . . 221, 226
\tab@mkpreamble@spc . . . . . . . . . . . 208

\tab@multicol . . . . . . . . . . . . . . . .

. . . . . 189, 342, 343, 437, 438, 569

\tab@normalstrut . . . . . 373, 401, \underline{416}

\tab@penalty . . . . . . . . . . . . . . 28,

450, 469, 527, 632, 650, 655, 660 \tab@pop . . . . . . . . . . . . . . . . \underline{168}, 178

\tab@postspcstate . . . . . . . . . 38, 142

\tab@poststate . . . . . . . . . . . . 37, 165 \tab@posttext . . . . . . . . . . . 7, 55,

60, 97, 102, 119, 143, 157, 166, 188 \tab@preamble . . . . . . . . . 4, 46, 50,

51, 182, 343, 346, 354, 438, 455, 571, 575, 963, 972, 1053, 1180, 1231

\tab@prepend . . . . . . . . . . 41, 162, 166 \tab@prespcstate 34, 73, 141, 145, 200 \tab@prestate . . . . . . . . 35, 161, 1230 \tab@pretext . . . . . . . . . . . . . . . 6,

52, 58, 91, 96, 117, 146, 156, 186 \tab@push . . . . . . . . . . . . . . . \underline{168}, 177 \tab@raisebase . . . . . . . . . . . 478, \underline{544}

\tab@readpreamble . . . . . . 14, \underline{198},

345, 353, 439, 574, 964, 1045, 1164

\tab@restorehlstate . . . . . . . 380, 429

\tab@right … 379, 462, 475, 488, 509 \tab@rightskip . . . . . . . . . . . . 18,

441, 455, 511–513, 516, 520, 524 \tab@rulefalse . . . . . . . . . . . . . . . 140 \tab@rulestate . . . . . 33, 121, 129, 201 \tab@ruletrue . . . . . . . . 103, 136, 194 \tab@setcr . . . . . . . . . . 436, 451, \underline{807} \tab@setposn . . . . . . . . . . . . . 442, \underline{457} \tab@setstate . . . . . . . . . . \underline{62}, 129,

142, 145, 155, 161, 165, 201, 1230 \tab@setstate@i . . . . . . . . . . . . 64, 68 \tab@setstrut . . . . . . . . 389, \underline{408}, 419 \tab@shortline . . . . . . . . . . . . . 5,

79, 98, 123, 131, 158, 183, 355, 709 \tab@startpause . . . . . . 190, 555, 941 \tab@startrow . . . . . . . . . . . . 437, \underline{537} \tab@startstate . . . . . . . . . . . 31, 181 \tab@state . . . . . . . . . . . . . . . . . .

. 2, 63, 69–71, 73, 82, 85, 121, 141, 151, 152, 181, 200, 652, 658, 662, 664, 666, 668, 671, 673

\tab@tabcr . . . . . . . . . . . . . . 786, 787 \tab@tabtext . . 47, 184, 962, 1042, 1163 \tab@tok 117, 119, 122, 130, 143, 146, 148 \tab@userpretext . . 8, 53, 59, 162, 187 \tab@vgapfalse . . . . . . . . . . . . . . . 711 \tab@vgaptrue . . . . . . . . . . . . . . . . 708 \tab@width . . . . . . . . . . . . . . . 14,

361, 396, 443, 446, 514, 519, 528 \tab@xcols . . . . . . . . . . . . . . 684, 698 \tabcolsep . . . . . . . . . . . . . . . . . . 399 \tabcoltype . . . . . . . . . . . . . . \underline{150},

279–281, 285–287, 292, 295, 298, 303, 985–988, 990, 1218–1220

\tabextrasep . . . . . . . . 10, 23, 27, 400 \tabpause . . . . . . . . . . . . . . . . . 6, \underline{552} \tabpause@i . . . . . . . . . . . . . . 559, 561 \tabruletype . . . . . . . . . \underline{114}, 289, 290 \tabruletype@i . . . . . . . . . . . 114, 115 \tabskip . . . . . . . . . . . . . . . . 110,

438, 453, 455, 962–964, 970, 973, 991, 1042, 1043, 1177, 1180

\tabspctype . . . . . . . \underline{138}, 291, 991, 992 \tabspctype@i . . . . . . . . . . . . 138, 139 \tabstyle . . . . . . . . . . . . . . . \underline{393}, 395 \tabular . . . . . . . . . . . . . . . . . . . . 404 tabular (environment) . . . . . . . . 5, \underline{404} tabular* (environment) . . . . . . . 5, \underline{404} \tabularnewline . . . . . . . . . . 450, 807

\tabuserposttype . . . . . . . . . \underline{160}, 302

\tabuserpretype . . . . . . . . . . \underline{160}, 301 \textfont . . . . . . . . . 1135, 1181, 1187 \textstyle 1147, 1199–1201, 1205–1207 \theequation . . . . . . . . 954, 1032, 1089 \thr@@ . . . . . . . . . . . . . . 25, 1169, 1173 \toks@ . . . . . . . . . . . . . . . . . 42, 49,

54, 169, 170, 1145, 1150, 1154, 1156 \toksdef . . . . . . . . . . . . . . . . . . . . 6–8

V

\vdots . . . . . . . . . . . . . . . . . . 24, \underline{1244} \ver@array . . . . . . . . . . . . . . . . . . 823 \ver@mdwtab . . . . . . . . . . . . . . . . . . 824 \vgap . . . . . . . . . . . . . . . . . . . . . 8, \underline{689} \vgap@after . . . . . . . . . . 692, 715, 749 \vgap@do . . . . . . . . . . . . 700, 704, 706 \vgap@i . . . . . . . . . . . . . . . . . 694, 696 \vgap@simple . . . . . . 694, 702, 750, 754 \vgap@spec . . . . . . . . . . 696, 697, 756 \vline . . . . . . . . . . . . . . . . . . . . 6, \underline{648}

\vrule . . . . . . . 289, 410, 648, 710, 797