%% This is part of the OpTeX project, see http://petr.olsak.net/optex \_codedecl \table {Macros for tables <2024-11-21>} % preloaded in format \_doc ----------------------------- The result of the \`\table``{}{}` macro is inserted into \`\_tablebox`. You can change default value if you want by `\let\_tablebox=\vtop` or `\let\_tablebox=\relax`. \_cod ----------------------------- \_let\_tablebox=\_vbox \_doc ----------------------------- We save the `to` or `pxto` to `#1` and \`\_tableW` sets the `to` to the \`\_tablew` macro. If `pxto` is used then `\_tablew` is empty and `\_tmpdim` includes given . The `\_ifpxto` returns true in this case. The `\table` continues by reading `{}` in the \^`\_tableA` macro. Catcodes (for example the `|` character) have to be normal when reading `\table` parameters. This is the reason why we use `\catcodetable` here. \_cod ----------------------------- \_newifi \_ifpxto \_def\_table#1#{\_tablebox\_bgroup \_tableW#1\_empty\_fin \_bgroup \_catcodetable\_optexcatcodes \_tableA} \_def\_tableW#1#2\_fin{\_pxtofalse \_ifx#1\_empty \_def\_tablew{}\_else \_ifx#1p \_def\_tablew{}\_tableWx#2\_fin \_else \_def\_tablew{#1#2}\_fi\_fi} \_def\_tableWx xto#1\_fin{\_tmpdim=#1\_relax \_pxtotrue} \_public \table ; \_doc ----------------------------- The \^`\tablinespace` is implemented by enlarging given \^`\tabstrut` by desired dimension (height and depth too) and by setting `\_lineskip=-2\_tablinespace`. Normal table rows (where no `\hrule` is between them) have normal baseline distance.\nl The \`\_tableA``{}` macro scans the `` by `\_scantabdata#1\_relax` and continues by processing `{}` by \^`\_tableB`. \_cod ----------------------------- \_def\_tableA#1{\_egroup \_the\_thistable \_global\_thistable={}% \_ea\_ifx\_ea^\_the\_tabstrut^\_setbox\_tstrutbox=\_null \_else \_setbox\_tstrutbox=\_hbox{\_the\_tabstrut}% \_setbox\_tstrutbox=\_hbox{\_vrule width\_zo height\_dimexpr\_ht\_tstrutbox+\_tablinespace depth\_dimexpr\_dp\_tstrutbox+\_tablinespace}% \_offinterlineskip \_lineskip=-2\_tablinespace \_fi \_colnum=0 \_let\_addtabitem=\_addtabitemx \_def\_tmpa{}\_tabdata={\_colnum1\_relax}\_scantabdata#1\_relax \_the\_everytable \_tableB } \_doc ----------------------------- The \`\_tableB` saves `` to `\_tmpb`. The `#1` parameter is re-scanned in order to set potential `#` characters inside it as others. This is needed because of `\gdef\_tmpb` and next \^`\replstring`s. They prefix each macro \^`\crl` (etc.) by `\_crcr`. See \^`\_tabreplstrings`. The whole `\_tableB` macro is hidden in `{...}` in order to there may be `\table` in `\table` and we want to manipulate with `&` and `\cr` as with normal tokens in the `\_tabreplstrings`, not as the item delimiters of an outer `\table`. The `\tabskip` value is saved for places between columns into the \`\_tabskipmid` macro. Then it runs \begtt \catcode`\<=13 \tabskip=\tabskipl \halign{\tabskip=\tabskipr \cr \crcr} \endtt This sets the desired boundary values of `\tabskip`. The \"between-columns" values are set as `\tabskip=`\^`\_tabskipmid` in the `` immediately after each column declarator. If `pxto` keyword was used, then we set the virtual unit \^`\tsize` to `-\hsize` first. Then the first attempt of the table is created in box 0. All columns where `p{..\tsize}` is used, are created as empty in this first pass. So, the `\wd0` is the width of all other columns. The \^`\_tsizesum` includes the sum of \^`\tsize`'s in `\hsize` units after first pass. The desired table width is stored in the `\_tmpdim`, so `\_tmpdim-\_wd0` is the rest which have to be filled by \^`\tsize`s. Then the \^`\tsize` is re-calculated and the real table is printed by `\halign` in the second pass. If no `pxto` keyword was used, then we print the table using `\halign` directly. The \^`\_tablew` macro is nonempty if the `to` keyword was used. The are re-tokenized by `\_scantextokens` in order to be more robust to catcode changing inside the . But inline verbatim cannot work in special cases here like \code{`\{`} for example. \_cod ----------------------------- \_long\_def\_tableB #1{% {{\_catcode`\#=12 % see OpTeX trick 0117: \_directlua{tex.sprint(-1,"\_luaescapestring{\_unexpanded{\_gdef\_tmpb{#1}}}")}}% \_tablereplstrings \_edef\_tabskipmid{\_the\_tabskip}\_tabskip=\_tabskipl \_ifpxto \_edef\_tsizes{\_global\_tsizesum=\_the\_tsizesum \_gdef\_noexpand\_tsizelast{\_tsizelast}}% \_tsizesum=\_zo \_def\_tsizelast{0}% \_tsize=-\_hsize \_setbox0=\_vbox{\_tablepxpreset \_halign \_tableC}% \_advance\_tmpdim by-\_wd0 \_ifdim \_tmpdim >\_zo \_else \_tsizesum=\_zo \_fi \_ifdim \_tsizesum >\_zo \_tsize =\_expr{\_number\_hsize/\_number\_tsizesum}\_tmpdim \_else \_tsize=\_zo \_fi \_tsizes % restoring values if there is a \table pxto inside a \table pxto. \_setbox0=\_null \_halign \_tableC \_else \_halign\_tablew \_tableC \_fi \_glet\_tmpb\_undefined }\_egroup % \_tablebox\_bgroup is in the \_table macro } \_def\_tableC{\_ea{\_the\_tabdata\_tabskip=\_tabskipr\_cr \_scantextokens\_ea{\_tmpb\_crcr}}} \_doc ----------------------------- \`\_tabreplstrings` replaces each `\crl` etc. to `\crcr\crl`. The reason is: we want to use macros that scan its parameter to a delimiter written in the right part of the table item declaration. The `\crcr` cannot be hidden in another macro in this case. \_cod ----------------------------- \_def\_tablereplstrings{% \_replstring\_tmpb{\crl}{\_crcr\crl}\_replstring\_tmpb{\crll}{\_crcr\crll}% \_replstring\_tmpb{\crli}{\_crcr\crli}\_replstring\_tmpb{\crlli}{\_crcr\crlli}% \_replstring\_tmpb{\crlp}{\_crcr\crlp}% } \_def\_tablepxpreset{} % can be used to de-activate references to .ref file \_newbox\_tstrutbox % strut used in table rows \_newtoks\_tabdata % the \halign declaration line \_doc ----------------------------- The \`\_scantabdata` macro converts `\table`'s `` to `\halign` ``. The result is stored into \`\_tabdata` tokens list. For example, the following result is generated when `=|cr||cl|`. \begtt tabdata: \_vrule\_the\_tabiteml{\_hfil#\_unsskip\_hfil}\_the\_tabitemr\_tabstrutA &\_the\_tabiteml{\_hfil#\_unsskip}\_the\_tabitemr \_vrule\_kern\_vvkern\_vrule\_tabstrutA &\_the\_tabiteml{\_hfil#\_unsskip\_hfil}\_the\_tabitemr\_tabstrutA &\_the\_tabiteml{\_relax#\_unsskip\_hfil}\_the\_tabitemr\_vrule\_tabstrutA ddlinedata: &\_dditem &\_dditem\_vvitem &\_dditem &\_dditem \endtt The second result in the \`\_ddlinedata` macro is a template of one row of the table used by \^`\crli` macro. \_cod ----------------------------- \_def\_scantabdata#1{\_let\_next=\_scantabdata \_ifx\_relax#1\_let\_next=\_relax \_else\_ifx|#1\_addtabvrule \_else\_ifx(#1\_def\_next{\_scantabdataE}% \_else\_ifx:#1\_def\_next{\_scantabdataF}% \_else\_isinlist{123456789}#1\_iftrue \_def\_next{\_scantabdataC#1}% \_else \_ea\_ifx\_csname _tabdeclare#1\_endcsname \_relax \_ea\_ifx\_csname _paramtabdeclare#1\_endcsname \_relax \_opwarning{tab-declarator "#1" unknown, ignored}% \_else \_def\_next{\_ea\_scantabdataB\_csname _paramtabdeclare#1\_endcsname}\_fi \_else \_def\_next{\_ea\_scantabdataA\_csname _tabdeclare#1\_endcsname}% \_fi\_fi\_fi\_fi\_fi\_fi \_next } \_def\_scantabdataA#1{\_addtabitem \_ea\_addtabdata\_ea{#1\_tabstrutA \_tabskip\_tabskipmid\_relax}\_scantabdata} \_def\_scantabdataB#1#2{\_addtabitem \_ea\_addtabdata\_ea{#1{#2}\_tabstrutA \_tabskip\_tabskipmid\_relax}\_scantabdata} \_def\_scantabdataC {\_def\_tmpb{}\_afterassignment\_scantabdataD \_tmpnum=} \_def\_scantabdataD#1{\_loop \_ifnum\_tmpnum>0 \_advance\_tmpnum by-1 \_addto\_tmpb{#1}\_repeat \_ea\_scantabdata\_tmpb} \_def\_scantabdataE#1){\_addtabdata{#1}\_scantabdata} \_def\_scantabdataF {\_addtabitem\_def\_addtabitem{\_let\_addtabitem=\_addtabitemx}\_scantabdata} \_doc ----------------------------- The \`\_addtabitemx` adds the boundary code (used between columns) to the . This code is `\egroup &\bgroup \colnum=\relax`. You can get the current number of column from the \`\colnum` register, but you cannot write `\the\colnum` as the first object in a item because `\halign` first expands the front of the item and the left part of the declaration is processed after this. Use `\relax\the\colnum` instead. Or you can write: \begtt \def\showcolnum{\ea\def\ea\totcolnum\ea{\the\colnum}\the\colnum/\totcolnum} \table{ccc}{\showcolnum & \showcolnum & \showcolnum} \endtt This example prints 1/3 \ 2/3 \ 3/3, because the value of the `\colnum` is equal to the total number of columns before left part of the column declaration is processed. \_cod ----------------------------- \_newcount\_colnum % number of current column in the table \_public \colnum ; \_def\_addtabitemx{\_ifnum\_colnum>0 \_addtabdata{&}\_addto\_ddlinedata{&\_dditem}\_fi \_advance\_colnum by1 \_let\_tmpa=\_relax \_ifnum\_colnum>1 \_etoksapp\_tabdata{\_colnum\_the\_colnum\_relax}\_fi} \_def\_addtabdata{\_toksapp\_tabdata} \_doc ----------------------------- This code converts `||` or `|` from `\table` to the . \_cod ----------------------------- \_def\_addtabvrule{% \_ifx\_tmpa\_vrule \_addtabdata{\_kern\_vvkern}% \_ifnum\_colnum=0 \_addto\_vvleft{\_vvitem}\_else\_addto\_ddlinedata{\_vvitem}\_fi \_else \_ifnum\_colnum=0 \_addto\_vvleft{\_vvitemA}\_else\_addto\_ddlinedata{\_vvitemA}\_fi\_fi \_let\_tmpa=\_vrule \_addtabdata{\_vrule}% } \_def\_tabstrutA{\_copy\_tstrutbox} \_def\_vvleft{} \_def\_ddlinedata{} \_doc ----------------------------- The default \"declaration letters" `c`, `l`, `r` and `p` are declared by setting \`\_tabdeclarec`, \`\_tabdeclarel`, \`\_tabdeclarer` and \^`\_paramtabdeclarep` macros. In general, define `\def\_tabdeclare{...}` for a non-parametric letter and `\def\_paramtabdeclare{...}` for a letter with a parameter. The double hash `##` must be in the definition, it is replaced by a real table item data. You can declare more such \"declaration letters" if you want. Note, that the `##` with fills are in group. The reason can be explained by following example: \begtt \table{|c|c|}{\crl \Red A & B \crl} \endtt We don't want vertical line after red A to be in red. \_cod ----------------------------- \_def\_tabdeclarec{\_the\_tabiteml \_hfil{##}\_unsskip \_hfil \_the\_tabitemr} \_def\_tabdeclarel{\_the\_tabiteml {##}\_unsskip \_hfil\_the\_tabitemr} \_def\_tabdeclarer{\_the\_tabiteml \_hfil{##}\_unsskip \_the\_tabitemr} \_doc ----------------------------- The \`\_paramtabdeclarep``{}` is invoked when `p{}` declarator is used. First, it saves the `\hsize` value and then it runs \`\_tablepar`. The \^`\_tablepar` macro behaves like \`\_tableparbox` (which is `\vtop`) in normal cases. But there is a special case: if the first pass of `pxto` table is processed then `\hsize` is negative. We print nothing in this case, i.e.\ \^`\_tableparbox` is \^`\ignoreit` and we advance the \`\_tsizesum`. The auxiliary macro \`\_tsizelast` is used to do advancing only in the first row of the table. \^`\_tsizesum` and \^`\_tsizelast` are initialized in the \^`\_tableB` macro. \_cod ----------------------------- \_def\_paramtabdeclarep#1{\_hsize=#1\_relax \_the\_tabiteml \_tablepar{\_tableparB ##\_tableparC}\_the\_tabitemr } \_def\_tablepar{% \_ifdim\_hsize<0pt \_ifnum\_tsizelast<\_colnum \_global\_advance\_tsizesum by-\_hsize \_xdef\_tsizelast{\_the\_colnum}\_fi \_let\_tableparbox=\_ignoreit \_fi \_tableparA \_tableparbox } \_let \_tableparbox=\_vtop \_let \_tableparA=\_empty \_newdimen \_tsizesum \_def \_tsizelast{0} \_doc ----------------------------- The \`\_tableparB` initializes the paragraphs inside the table item and \`\_tableparC` closes them. They are used in the \^`\_paramtabdeclarep` macro. The first paragraph is no indented. \_cod ----------------------------- \_def\_tableparB{% \_baselineskip=\_normalbaselineskip \_lineskiplimit=\_zo \_noindent \_unless\_ifx\_tabstrutA\_empty \_raise\_ht\_tstrutbox\_null \_fi \_hskip\_zo \_relax } \_def\_tableparC{% \_unsskip \_unless\_ifx\_tabstrutA\_empty \_ifvmode\_vskip\_dp\_tstrutbox \_else\_lower\_dp\_tstrutbox\_null\_fi \_fi } \_doc ----------------------------- Users put optional spaces around the table item typically, i.e.\ they write `& text &` instead `&text&`. The left space is ignored by the internal \TeX/ algorithm but the right space must be removed by macros. This is a reason why we recommend to use \`\_unsskip` after each `##` in your definition of \"declaration letters". This macro isn't only the primitive `\unskip` because we allow usage of plain \TeX/ `\hideskip` macro: `&\hideskip text\hideskip&`. \_cod ----------------------------- \_def\_unsskip{\_ifmmode\_else\_ifdim\_lastskip>\_zo \_unskip\_fi\_fi} \_doc ----------------------------- The \`\fL`, \`\fR`, \`\fC` and \`\fX` macros only do special parameters settings for paragraph building algorithm. \_cod \_let\_fL=\_raggedright \_def\_fR{\_leftskip=0pt plus 1fill \_relax} \_def\_fC{\_leftskip=0pt plus1fill \_rightskip=0pt plus 1fill \_relax} \_def\_fX{\_leftskip=0pt plus1fil \_rightskip=0pt plus-1fil \_parfillskip=0pt plus2fil \_relax} \_public \fL \fR \fC \fX ; \_doc ----------------------------- The \`\fS` macro is more tricky. The \^`\_tableparbox` isn't printed immediately, but `\setbox2=` is prefixed by the macro \`\_tableparA`, which is empty by default (used in \^`\_tablepar`). The \`\_tableparD` is processed after the box is set: it checks if there is only one line and prints `\hbox to\hsize{\hfil\hfil}` in this case. In other cases, the box2 is printed. \_cod ----------------------------- \_def\_fS{\_relax \_ifdim\_hsize<0pt \_else \_def\_tableparA{\_setbox2=}\_fi \_addto\_tableparC{\_aftergroup\_tableparD}% } \_def\_tableparD{\_setbox0=\_vbox{\_unvcopy2 \_unskip \_global\_setbox1=\_lastbox}% \_ifdim\_ht0>0pt \_box2 \_setbox0=\_box1 \_else \_hbox to\_hsize{\_hfil \_unhbox1\_unskip\_unskip\_hfil}\_setbox0=\_box2 \_fi } \_public \fS ; \_doc ----------------------------- The family of `\_cr*` macros \`\crl`, \`\crll`, \`\crli`, \`\crlli`, \`\crlp` and \`\tskip` `` is implemented here. The \`\_zerotabrule` is used to suppress the negative `\lineskip` declared by \^`\tablinespace`. \_cod ----------------------------- \_def\_crl{\_crcr\_noalign{\_hrule}} \_def\_crll{\_crcr\_noalign{\_hrule\_kern\_hhkern\_hrule}} \_def\_zerotabrule {\_noalign{\_hrule height\_zo width\_zo depth\_zo}} \_def\_crli{\_crcr \_zerotabrule \_omit \_gdef\_dditem{\_omit\_tablinefil}\_gdef\_vvitem{\_kern\_vvkern\_vrule}\_gdef\_vvitemA{\_vrule}% \_vvleft\_tablinefil\_ddlinedata\_crcr \_zerotabrule} \_def\_crlli{\_crli\_noalign{\_kern\_hhkern}\_crli} \_def\_tablinefil{\_leaders\_hrule\_hfil} \_def\_crlp#1{\_crcr \_zerotabrule \_noalign{\_kern-\_drulewidth}% \_omit \_xdef\_crlplist{#1}\_xdef\_crlplist{,\_ea}\_ea\_crlpA\_crlplist,\_fin,% \_global\_tmpnum=0 \_gdef\_dditem{\_omit\_crlpD}% \_gdef\_vvitem{\_kern\_vvkern\_kern\_drulewidth}\_gdef\_vvitemA{\_kern\_drulewidth}% \_vvleft\_crlpD\_ddlinedata \_global\_tmpnum=0 \_crcr \_zerotabrule} \_def\_crlpA#1,{\_ifx\_fin#1\_else \_crlpB#1-\_fin,\_ea\_crlpA\_fi} \_def\_crlpB#1#2-#3,{\_ifx\_fin#3\_xdef\_crlplist{\_crlplist#1#2,}\_else\_crlpC#1#2-#3,\_fi} \_def\_crlpC#1-#2-#3,{\_tmpnum=#1\_relax \_loop \_xdef\_crlplist{\_crlplist\_the\_tmpnum,}\_ifnum\_tmpnum<#2\_advance\_tmpnum by1 \_repeat} \_def\_crlpD{\_incr\_tmpnum \_edef\_tmpa{\_noexpand\_isinlist\_noexpand\_crlplist{,\_the\_tmpnum,}}% \_tmpa\_iftrue \_kern-\_drulewidth \_tablinefil \_kern-\_drulewidth\_else\_hfil \_fi} \_def\_tskip{\_afterassignment\_tskipA \_tmpdim} \_def\_tskipA{\_gdef\_dditem{}\_gdef\_vvitem{}\_gdef\_vvitemA{}\_gdef\_tabstrutA{}% \_vbox to\_tmpdim{}\_ddlinedata \_crcr \_zerotabrule \_noalign{\_gdef\_tabstrutA{\_copy\_tstrutbox}}} \_public \crl \crll \crli \crlli \crlp \tskip ; \_doc ----------------------------- The \`\mspan``{}[]{}` macro generates similar `\omit\span\omit\span` sequence as plain \TeX/ macro `\multispan`. Moreover, it uses \^`\_scantabdata` to convert `` from `\table` syntax to `\halign` syntax. \_cod ----------------------------- \_def\_mspan{\_omit \_afterassignment\_mspanA \_mscount=} \_def\_mspanA[#1]#2{\_loop \_ifnum\_mscount>1 \_cs{_span}\_omit \_advance\_mscount-1 \_repeat \_count1=\_colnum \_colnum=0 \_def\_tmpa{}\_tabdata={}\_scantabdata#1\_relax \_colnum=\_count1 \_setbox0=\_vbox{\_halign\_ea{\_the\_tabdata\_cr#2\_cr}% \_global\_setbox8=\_lastbox}% \_setbox0=\_hbox{\_unhbox8 \_unskip \_global\_setbox8=\_lastbox}% \_unhbox8 \_ignorespaces} \_public \mspan ; \_doc ----------------------------- The \`\vspan``{}` implementation is here. We need to lower the box by \begtt \catcode`\<=13 (-1)*(\ht+\dp of \tabstrut) / 2. \endtt The `#1` parameter must be a one-digit number. If you want to set more digits then use braces. \_cod ----------------------------- \_def\_vspan#1#2#{\_vspanA{#1#2}} \_def\_vspanA#1#2{\_vtop to\_zo{\_hbox{\_lower \_dimexpr #1\_dimexpr(\_ht\_tstrutbox+\_dp\_tstrutbox)/2\_relax -\_dimexpr(\_ht\_tstrutbox+\_dp\_tstrutbox)/2\_relax \_hbox{#2}}\_vss}} \_public \vspan ; \_doc ----------------------------- The parameters of primitive `\vrule` and `\hrule` keeps the rule \"last wins". If we re-define `\hrule` to `\_orihrule height1pt` then each usage of redefined `\hrule` uses `1pt` height if this parameter isn't overwritten by another following `height` parameter. This principle is used for settings another default rule thickness than 0.4\,pt by the macro \`\rulewidth`. \_cod ----------------------------- \_newdimen\_drulewidth \_drulewidth=0.4pt \_let\_orihrule=\_hrule \_let\_orivrule=\_vrule \_def\_rulewidth{\_afterassignment\_rulewidthA \_drulewidth} \_def\_rulewidthA{\_edef\_hrule{\_orihrule height\_drulewidth}% \_edef\_vrule{\_orivrule width\_drulewidth}% \_let\_rulewidth=\_drulewidth \_public \vrule \hrule \rulewidth;} \_public \rulewidth ; \_doc ----------------------------- The \`\frame``{}` uses \"\code{\\vbox} in \code{\\vtop}" trick in order to keep the baseline of the internal text at the same level as outer baseline. User can write `\frame{abcxyz}` in normal paragraph line, for example and gets the expected result: \frame{abcxyz}. The internal margins are set by `\vvkern` and `\hhkern` parameters. \_cod ----------------------------- \_long\_def\_frame#1{% \_hbox{\_vrule\_vtop{\_vbox{\_hrule\_kern\_vvkern \_hbox{\_kern\_hhkern{#1}\_kern\_hhkern}% }\_kern\_vvkern\_hrule}\_vrule}} \_public \frame ; \_doc ----------------------------- \`\eqbox` and \`\eqboxsize` are implemented here. The widths of all `\eqbox`es are saved to the `.ref` file in the format \`\_Xeqbox``{