In plain TeX, there is a macro \bordermatrix
but I am not very satisfied with
it. The problem of that is, the “border” is always at the leftmost and topmost,
but not any other combinations. For some unknown reason, I am surprised that no
one has any problem with it and no one has ever developed a new package to
remediate this problem. Thus I do my own.
Dissection of the legacy bordermatrix macro
The code for legacy \bordermatrix
, as from the TeXbook, is below
\newdimen\p@renwd \setbox0=\hbox{\tenex B} \p@renwd=\wd0
\def\bordermatrix#1{\begingroup \m@th
\setbox0=\vbox{\def\cr{\crcr\noalign{\kern2pt\global\let\cr=\endline}}
\ialign{$##$\hfil\kern2pt\kern\p@renwd&\thinspace\hfil$##$\hfil
&&\quad\hfil$##$\hfil\crcr
\omit\strut\hfil\crcr\noalign{\kern-\baselineskip}
#1\crcr\omit\strut\cr}}
\setbox2=\vbox{\unvcopy0 \global\setbox1=\lastbox}
\setbox2=\hbox{\unhbox1 \unskip \global\setbox1=\lastbox}
\setbox2=\hbox{$\kern\wd1\kern-\p@renwd \left( \kern-\wd1
\global\setbox1=\vbox{\box1\kern2pt}
\vcenter{\kern-\ht1 \unvbox0 \kern-\baselineskip} \,\right)$}
\null\;\vbox{\kern\ht1\box2}\endgroup}
The code is explained below:
% Define the width of a bracket as \p@renwd
\newdimen\p@renwd \setbox0=\hbox{\tenex B} \p@renwd=\wd0
% Define the macro \bordermatrix
\def\bordermatrix#1{
\begingroup
\m@th % Set the mathsurround stuff
%
% Put the matrix into box 0, without brackets
\setbox0=\vbox{
% Redefine \cr to provide inter-row gap
\def\cr{\crcr\noalign{\kern2pt\global\let\cr=\endline}}
% Matrix as an \ialign
\ialign{
$##$\hfil\kern2pt\kern\p@renwd& % First column: Big space at right
\thinspace\hfil$##$\hfil&& % Second column: Small space at left
\quad\hfil$##$\hfil % All other columns: space at left as inter-column gap
\crcr % End of \ialign template
\omit\strut\hfil\crcr % Small space to separate matrix from the line above
\noalign{\kern-\baselineskip}
#1\crcr % The matrix as provided by the user
\omit\strut\cr % One empty cell as the last row
}
}
%
% Determine the first column's width:
% Firstly, extract box0 for its last box inside, i.e. the last row
% Then, unbox the last row to find the last (i.e. leftmost) column, save as box 1
% Afterwards, the width of box 1 is taken as the width of first column
\setbox2=\vbox{\unvcopy0 \global\setbox1=\lastbox}
\setbox2=\hbox{\unhbox1 \unskip \global\setbox1=\lastbox}
%
% Put the bracket into the matrix, and save the result as an \hbox
\setbox2=\hbox{$ % Start math mode
$\kern\wd1\kern-\p@renwd % Shift right the first column's width minus bracket's width
\left( % Print the left bracket
\kern-\wd1 % Shift back the first column's width
\global\setbox1=\vbox{\box1\kern2pt} % Save box 1's height + 2pt
\vcenter{ % Make a vbox to print the matrix in correct position
\kern-\ht1 % Shift upward a box 1's height as the topmost row should not inside bracket
\unvbox0 % Print the matrix
\kern-\baselineskip % Subtract a row's height from the calculation of the matrix's height
} % Now this vbox's height is one row shorter then box 0
\,\right) % Leave a tiny space and print the right bracket
$}
\null\; % Print some small space
\vbox{\kern\ht1\box2} % Then the bracketed matrix shifted down one row to avoid
% the topmost border not overprinting existing text
\endgroup
}
Related work
Herbert Voss wrote a manual “Math mode” that describes an enhanced \bordermatrix
so that we can provide user-specified bracket type and also it provided
\bordermatrix*
as an derived version that puts the border at rightmost column
and bottommost row.
K. Border wrote a \kbordermatrix
that allows the border to be in different font style.
qbordermatrix
My work is a more generic version. It allows any combination of the four sides to be the border.
% qbordermatrix.sty - A more flexible \bordermatrix for LaTeX
% Copyright 2010 (c) Adrian S. Tam <adrian.sw.tam@gmail.com>
%
% This LaTeX package provides three commands, namely,
% \bordermatrix - similar to the Knuth's version
% \bordermatrix* - adapted from the manual "Math mode" by Herbert Voss
% that provides the border at bottom and right instead
% \qbordermatrix - Quadri-border matrix, you can select any combination
% of the four borders to put outside of the matrix
%
% The \qbordermatrix macro takes the syntax of the following
% \qbordermatrix[#1]{#2}{
% a11 & a12 & a13 & a14
% a21 & a22 & a23 & a24
% a31 & a32 & a33 & a34
% }
% The optional parameter #1 provides two characters as the matrix's left and
% right delimiters. The parameter #2 provides a subset of "n", "s", "e", "w"
% to tell which border should be outside of the matrix. Then the matrix is
% provided in the last curly bracket.
%
% In this sense, \bordermatrix is same as \qbordermatrix{nw} and
% \bordermatrix* is same as \qbordermatrix{se}. An example is given at
% the bottom of this file.
%
% Compared to the Knuth's version, the matrices constructed by this package
% has the following properties
% 1. The row height is determined dynamically. Thus if the border row is
% tall, e.g. a display fraction, the bracket is placed in the correct
% size.
% 2. The matrix is aligned at the middle of the bracket, regardless the
% heights of the matrix or the border rows
% 3. Different matrix delimiters are allowed, as long as they can fit into
% the \left and \right pairs in equations
% 4. The output is in a vbox and the "canva" covers exactly the matrix
% Also the spacing around the matrix is a bit tight.
%
% Please send your comments and enhancement ideas to adrian.sw.tam@gmail.com
%
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{qbordermatrix}[2010/3/24 qbordermatrix v1.0]
\makeatletter
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Support of \bordermatrix and \bordermatrix* as described in "Math mode - v.2.43" by
% Herbert Voss
%
\newdimen\matrixrowsep
\matrixrowsep=2pt
% Determine if an asterisk is following \bordermatrix, and assert \@borderstar
\def\bordermatrix{\@ifnextchar*{\@bordermatrix@se}{\@bordermatrix@nw}}
% If no bracket is provided, use parenthesis
\def\@bordermatrix@se*{\@ifnextchar[{\@bordermatrix@se@i}{\@bordermatrix@se@i[()]}}
\def\@bordermatrix@nw{\@ifnextchar[{\@bordermatrix@nw@i}{\@bordermatrix@nw@i[()]}}
% Construction of the bordermatrix
\def\@bordermatrix@se@i[#1]#2{\@qbordermatrix@i[#1]{se}{#2}}
\def\@bordermatrix@nw@i[#1]#2{\@qbordermatrix@i[#1]{nw}{#2}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% More flexible \qbordermatrix macro
%
\newif\if@north
\newif\if@south
\newif\if@east
\newif\if@west
\def\@swallow#1{}
% If no bracket is provided, use parenthesis
\def\qbordermatrix{\@ifnextchar[{\@qbordermatrix@i}{\@qbordermatrix@i[()]}}
% Construction of the bordermatrix
\def\@qbordermatrix@i[#1]#2#3{%
\begingroup
% Parse parameters
\@northfalse\@southfalse\@eastfalse\@westfalse
\in@{n}{#2}\ifin@\@northtrue\fi
\in@{s}{#2}\ifin@\@southtrue\fi
\in@{e}{#2}\ifin@\@easttrue\fi
\in@{w}{#2}\ifin@\@westtrue\fi
\m@th % mathsurround stuff
% compute bracket width
\setbox\z@\hbox{$\left\@firstoftwo#1\right.$}
\newdimen\@leftbwd
\@leftbwd\wd\z@
\setbox\z@\hbox{$\left.\right\@secondoftwo#1$}
\newdimen\@rightbwd
\@rightbwd\wd\z@
% Define carriage-return for array rows
\def\cr{\crcr\noalign{\kern\matrixrowsep\global\let\cr\endline}} % for a safer \cr
\def\\{\cr}
% Set box0 to contain the matrix without bracket
\setbox\z@\vbox{%
\ialign{ % the matrix
\hfil$##$\hfil\kern 2\p@\kern\@leftbwd & % first column: big space at right for open bracket
\thinspace\hfil $##$\hfil && % second column: small space at left
\quad\hfil $##$\hfil % all other columns: big space at left for column separation
\crcr % end of preamble
#3\crcr % the matrix itself
}
}%
% Determine the last row height
% Extract last row from box 0 and measure its height
\setbox\thr@@\vbox{\unvcopy\z@\global\setbox\tw@\lastbox}
\setbox\tw@\vbox{\hrule\box\tw@\hrule}
\newdimen\@lastrowht
\@lastrowht\ht\tw@
% Determine the first row height
% by removing one row at a time from the content of box 0 (copied to box 3).
% If, after removing a row, the height of the box is less than 4pt, we rollback
% one step and that is the first row's height
\loop
\ifdim\ht\thr@@>0pt
\setbox\tw@\copy\thr@@
\setbox\thr@@\vbox{%
\unvbox\thr@@
\loop% Remove kern, skip, and penalty until we found a box to remove
\unkern\unskip\unpenalty
\global\setbox\@ne\lastbox
\ifvoid\@ne
\repeat
}
\repeat
\setbox\tw@\vbox{\hrule\copy\@ne\hrule} % use \copy\tw@ for taller height
\newdimen\@firstrowht
\@firstrowht\ht\tw@
% Determine the last column width
% After above, the first row is stored in box 1 and we extract the last
% column and meausre its width
\setbox\tw@\hbox{%
\unhcopy\@ne
\loop
\unkern\unskip\unpenalty
\global\setbox\thr@@\lastbox
\ifvoid\thr@@
\repeat
}
\newdimen\@lastcolwd
\@lastcolwd\wd\thr@@
% Determine the first column width
% The entire first row is stored in box 1 and we do the similar iterations
% to remove one box at a time from box 1 to find the first column width
\loop
\setbox\tw@\copy\@ne
\setbox\@ne\hbox{%
\unhbox\@ne
\loop
\unkern\unskip\unpenalty
\setbox\thr@@\lastbox
\ifvoid\thr@@
\repeat
}
\ifdim\wd\@ne>0pt
\repeat
\newdimen\@firstcolwd
\@firstcolwd\wd\tw@
% Put the bracket into the matrix:
% First right shift for first column's width, and put the opening bracket, then
% left shift to original position to put the matrix, and then shift left to close
% the bracket and shift back to right.
% The matrix, however, is also shifted to reflect the desired height of bracket,
\setbox\tw@\hbox{$
\if@west\kern\@firstcolwd\kern-\@leftbwd\fi
\left\@firstoftwo#1
\if@west\kern-\@firstcolwd\fi
\vcenter{%
\if@north\kern-\@firstrowht\fi
\unvcopy\z@
\if@south\kern-\@lastrowht\fi
}
\if@east\kern-\@lastcolwd\fi
\right\@secondoftwo#1
\if@east\kern-\@rightbwd\kern\@lastcolwd\fi
$}
% Print the result
\if@north\setbox\tw@\vbox{\kern\@firstrowht\box\tw@}\fi
\if@south\setbox\tw@\vtop{\box\tw@\kern\@lastrowht}\fi
\box\tw@
\endgroup
}
\makeatother
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Example:
%
% \documentclass{article}
% \usepackage{qbordermatrix}
% \begin{document}
% \hrule y\vrule$\bordermatrix[\{\}]{%
% 0 & 1 &2^{2^2}& 3 & \displaystyle\frac{4}{2} \cr
% a & x_1 & y_2 & z_1 & t_1 \cr
% b & x_3 & y_4 & z_2 & t_2 \cr
% c & x_5 & y_6 & z_3 & t_3 \cr
% d & 1 &2^{2^2}& 3 & \displaystyle\frac{4}{2} \cr
% }$\vrule z\hrule
%
% \hrule y\vrule$\bordermatrix*[\{\}]{%
% 0 & 1 &2^{2^2}& 3 & \displaystyle\frac{4}{2} \cr
% a & x_1 & y_2 & z_1 & t_1 \cr
% b & x_3 & y_4 & z_2 & t_2 \cr
% c & x_5 & y_6 & z_3 & t_3 \cr
% d & 1 &2^{2^2}& 3 & \displaystyle\frac{4}{2} \cr
% }$\vrule z \hrule
%
% \hrule y\vrule$\qbordermatrix[\{\}]{nesw}{%
% 0 & 1 &2^{2^2}& 3 & \displaystyle\frac{4}{2} \cr
% a & x_1 & y_2 & z_1 & t_1 \cr
% b & x_3 & y_4 & z_2 & t_2 \cr
% c & x_5 & y_6 & z_3 & t_3 \cr
% d & 1 &2^{2^2}& 3 & \displaystyle\frac{4}{2} \cr
% }$\vrule z
% \hrule
% \end{document}
%