%% This is part of the OpTeX project, see http://petr.olsak.net/optex \_codedecl \table {Basic macros for OpTeX <2021-03-09>} % 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\_end \_bgroup \_catcodetable\_optexcatcodes \_tableA} \_def\_tableW#1#2\_end{\_pxtofalse \_ifx#1\_empty \_def\_tablew{}\_else \_ifx#1p \_def\_tablew{}\_tableWx#2\_end \_else \_def\_tablew{#1#2}\_fi\_fi} \_def\_tableWx xto#1\_end{\_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`. The trick `\_tmptoks={}\_edef\_tmpb{\_the\_tmptoks}` is used here in order to keep the hash marks in the unchanged. \_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 \_bgroup \_catcode`\#=12 \_tableB } \_doc ----------------------------- The \`\_tableB` saves `` to `\_tmpb` and does four \^`\replstring`s to prefix each macro \^`\crl` (etc.) by `\_crcr`. The reason is: we want to use macros that scan its parameter to the delimiter written in the right part of the table item declaration. See \^`\fS` for example. The `\crcr` cannot be hidden in another macro in this case. 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. Then the \^`\tsize` is re-calculated using `\wd0` 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. Because the color selector with `\aftergroup` can be used inside the table item, we must create the second real group for each table item. This is reason why we start `` by `\bgroup` and we end it by `\egroup` in the \`\_tableC` macro. Each `&` character is stored as `\egroup&\bgroup` in ``. The `\halign\_tablew\_tableC` really does: \begtt \catcode`\<=13 \halign\_tablew{\bgroup\egroup\tabskip=\tabskipr \cr\crcr} \endtt \relax 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{\_egroup \_def\_tmpb{#1}% \_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}% \_edef\_tabskipmid{\_the\_tabskip}\_tabskip=\_tabskipl \_ifpxto \_tsize=\_hsize \_setbox0 = \_vbox{\_tablepxpreset \_halign \_tableC}% \_tsize=\_dimexpr\_hsize-(\_wd0-\_tmpdim)\_relax \_setbox0=\_null \_halign \_tableC \_else \_halign\_tablew \_tableC \_fi \_egroup } \_def\_tableC{\_ea{\_ea\_bgroup\_the\_tabdata\_egroup\_tabskip=\_tabskipr\_cr \_scantextokens\_ea{\_tmpb\_crcr}}} \_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#\_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{\_egroup &\_bgroup}\_addto\_ddlinedata{&\_dditem}\_fi \_advance\_colnum by1 \_let\_tmpa=\_relax \_ifnum\_colnum>1 \_ea\_addtabdata\_ea{\_ea\_colnum\_the\_colnum\_relax}\_fi} \_def\_addtabdata#1{\_tabdata\_ea{\_the\_tabdata#1}} \_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. \_cod ----------------------------- \_def\_tabdeclarec{\_the\_tabiteml\_hfil##\_unsskip\_hfil\_the\_tabitemr} \_def\_tabdeclarel{\_the\_tabiteml\_relax##\_unsskip\_hfil\_the\_tabitemr} \_def\_tabdeclarer{\_the\_tabiteml\_hfil##\_unsskip\_the\_tabitemr} \_def\_paramtabdeclarep#1{\_the\_tabiteml \_vtop{\_hsize=#1\_relax \_baselineskip=\_normalbaselineskip \_lineskiplimit=\_zo \_noindent##\_unsskip \_ifvmode\_vskip\_dp\_tstrutbox \_else\_lower\_dp\_tstrutbox\_hbox{}\_fi}\_the\_tabitemr} \_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. The \`\fS` prints the paragraph into box 0 first, measures the number of lines by the `\prevgraf` primitive and use (or don't use) `\hfil` (for centering) before the first line. \_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} \_long\_def\_fS #1\_unsskip{\_noindent \_setbox0 =\_vbox{\_noindent #1\_endgraf \_ea}% \_ifnum\_prevgraf=1 \_hfil \_fi #1\_unsskip } \_public \fL \fR \fC \fX \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,\_end,% \_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\_end#1\_else \_crlpB#1-\_end,\_ea\_crlpA\_fi} \_def\_crlpB#1#2-#3,{\_ifx\_end#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{\_ea\_bgroup\_the\_tabdata\_egroup\_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\_relax#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``{