% \iffalse meta-comment % vim: tw=80 spl=en % %% File: cellprops.dtx (C) Copyright 2016-2021 RIVAUD Julien %% %% It may be distributed and/or modified under the conditions of the %% General Public License (GPL), either version 3 of this %% license or (at your option) any later version. % %<*driver|package> % The version of expl3 required is tested as early as possible, as % some really old versions do not define \ProvidesExplPackage. \NeedsTeXFormat{LaTeX2e}[1995/12/01] \RequirePackage{expl3}[2018/06/19] \def\ExplFileName{cellprops} \def\ExplFileDescription{CSS-like cell and table properties} \def\ExplFileDate{2021/01/30} \def\ExplFileVersion{2.0} % %<*driver> \documentclass[full]{l3doc} \usepackage[english]{babel} \usepackage{longtable} \usepackage{cellprops} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \textsf{\ExplFileName} package\\ \ExplFileDescription^^A % \thanks{This file describes v\ExplFileVersion, % last revised \ExplFileDate.}^^A % } % % \author{^^A % Julien ``\_FrnchFrgg\_'' \textsc{Rivaud}\thanks % {^^A % E-mail: % \href{mailto:frnchfrgg@free.fr} % {frnchfrgg@free.fr}^^A % }^^A % } % % \date{Released \ExplFileDate} % % \maketitle % % \begin{documentation} % % % % \section{\pkg{\ExplFileName} documentation} % % This package reworks the internals of \env{tabular}, \env{array}, and similar % constructs, and adds a \cs{cellprops} command accepting CSS-like selectors and % properties. It implements the "border-collapse: separate" CSS model. % % It depends on \pkg{mdwtab}, \pkg{xcolor} and of course \pkg{expl3} and % \pkg{xparse}. % % \pkg{cellprops} default settings mimick the LaTeX layout, that is left and % right padding equal to "\tabcolsep" or "\arraycolsep", zero top and bottom % padding, but minimum height and depth corresponding to the table strut box. % % I recommend to add globally: %\begin{verbatim} % \cellprops{ td { padding: 1ex; min-height: 0pt; min-depth: 0pt; } } %\end{verbatim} % so that you get better-looking tables by default. % % \subsection{Examples} % % To produce the (arguably ugly): % \[ % \cellprops{ % td { % padding: 1ex; % min-height: 0pt; % min-depth: 0pt; % border-style: none solid solid none; % text-align: center; % } % table { % background-color: black!5!white; % } % tr:nth-child(even) { % background-color: black!15!white; % } % td:nth-child(even) { % background-color: yellow!20!white % } % tr:nth-child(even) td:nth-child(even) { % background-color: yellow!50!white; % } % tr:first-child td { % border-top-style: solid; % } % td:first-child { % border-left-style: solid; % math-mode: text; % text-align: left; % } % tr:nth-child(6) td:nth-child(1) { % vertical-align: top; % see remark in usage guide % } % tr:nth-child(6) td:nth-child(2) { % vertical-align: middle; % } % tr:nth-child(6) td:nth-child(3) { % vertical-align: bottom; % see remark in usage guide % } % } % \begin{array}{nnnp{5em}} % This is text & A_2 & A_3 & A_4 \\ % B1 & This is maths & B_3 & \\ % C1 & C_2 & X & Y \\ % D1 & D_2 & DX & v \\ % & F & \int_a^b f(t) dt & v \\ % \fbox{Top} & \fbox{Middle} & \fbox{Bottom} & \fbox{Baseline}% % \textcolor{red}{% % \kern -9cm\relax % \vrule height 0.1pt depth 0.1pt width 10cm % \kern -1cm\relax % }\textcolor{blue}{% % \kern -9cm\relax % \vrule height 0.52ex depth -0.48ex width 10cm % \kern -1cm\relax % } \\ % \end{array} % \] % you can use: %\begin{verbatim} % \[ % \cellprops{ % td { % padding: 1ex; % min-height: 0pt; % min-depth: 0pt; % border-style: none solid solid none; % text-align: center; % } % table { % background-color: black!5!white; % } % tr:nth-child(even) { % background-color: black!15!white; % } % td:nth-child(even) { % background-color: yellow!20!white % } % tr:nth-child(even) td:nth-child(even) { % background-color: yellow!50!white; % } % tr:first-child td { % border-top-style: solid; % } % td:first-child { % border-left-style: solid; % math-mode: text; % text-align: left; % } % tr:nth-child(6) td:nth-child(1) { % vertical-align: top; % see remark in usage guide % } % tr:nth-child(6) td:nth-child(2) { % vertical-align: middle; % } % tr:nth-child(6) td:nth-child(3) { % vertical-align: bottom; % see remark in usage guide % } % } % \begin{array}{nnnp{5em}} % This is text & A_2 & A_3 & A_4 \\ % B1 & This is maths & B_3 & \\ % C1 & C_2 & X & Y \\ % D1 & D_2 & DX & v \\ % & F & \int_a^b f(t) dt & v \\ % \fbox{Top} & \fbox{Middle} & \fbox{Bottom} & \fbox{Baseline}% % \textcolor{red}{% % \kern -9cm\relax % \vrule height 0.1pt depth 0.1pt width 10cm % \kern -1cm\relax % }\textcolor{blue}{% % \kern -9cm\relax % \vrule height 0.52ex depth -0.48ex width 10cm % \kern -1cm\relax % } \\ % \end{array} % \] %\end{verbatim} % % You can also use the \env{longtable} environment: % %\begingroup % %\cellprops{ % td { border: thin solid black; } % tr:nth-child(-n+3) { background-color: black!10; } % tr:nth-child(n+8) { background-color: blue!10; } % tr:nth-child(4n) td:first-child, % tr:nth-child(4n+1) td:nth-child(2), % tr:nth-child(4n+2) td:nth-child(3), % tr:nth-child(4n+3) td:nth-child(4) { % border: thick solid red; % } %} %\begin{longtable}{nnnn} % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ % aaaaa & baaaa & caaaaa & dbbbb \\ %\end{longtable} % %\endgroup % % This table has been produced by: %\begin{verbatim} % \cellprops{ % td { border: thin solid black; } % tr:nth-child(-n+3) { background-color: black!10; } % tr:nth-child(n+8) { background-color: blue!10; } % tr:nth-child(4n) td:first-child, % tr:nth-child(4n+1) td:nth-child(2), % tr:nth-child(4n+2) td:nth-child(3), % tr:nth-child(4n+3) td:nth-child(4) { % border: thick solid red; % } % } % \begin{longtable}{nnnn} % aaaaa & baaaa & caaaaa & dbbbb \\ % ... % aaaaa & baaaa & caaaaa & dbbbb \\ % \end{longtable} %\end{verbatim} % % \subsection{Usage guide} % % \def\OR{\string| } % % \def\bracket#1{\<#1>:} % \def\<#1>{{\normalfont<\textsl{#1}>}} % \begin{description}[format=\bracket, % wide,leftmargin=9em,labelwidth=!,labelsep=0pt, % ] % \item[usage] "'\cellprops{'" [ \ "'{'" \ "'}'" ]* "'}'" % \item[selectors] \ ["," \ ] % \item[selector] [\ "' '"] [\ "' '"] [\
"' '"] [\] % \item[table] "'table'" ["'.'" \]* ["':where(.'"\"')'"]* % \OR "':where('"\"')'" % \item[tr] "'tr'" [\]* \OR "':where('"\"')'" % \item[td] "'td'" [\]* \OR "':where('"\
"')'" % \item[parbox] "'p'" \OR "':where('"\

"')'" % \item[pseudoclass] "':where('"[\]*"')'" \OR "':nth-child('"\"')" % \item[nth] % "'odd'" \OR "'even'" \OR \ \OR % \"'n+'"\ \OR \"'n-'"\ % \item[properties] [ \ "';'" ]* % \item[property] % "'padding: '" ( \ ) \{1,4\} \OR \\ % "'padding-top: '" \ \OR \\ % "'padding-right: '" \ \OR \\ % "'padding-bottom: '" \ \OR \\ % "'padding-left: '" \ \OR \\ % "'min-height: '" \ \OR \\ % "'min-depth: '" \ \OR \\ % "'min-width: '" \ \OR \\ % "'text-align: '" ( "'left'" \OR "'right'" \OR "'center'" ) \OR \\ % "'vertical-align: '" ( "'top'" \OR "'middle'" \OR "'baseline'" \OR "'bottom'" ) \OR \\ % "'math-mode: '" ( "'text'" \OR "'math'" \OR "'auto'" ) \OR \\ % "'color: '" \ \OR \\ % "'background-color: '" ( \ \OR "'transparent'" ) \OR \\ % "'border: '" [ \ ] [ \ ] [ \ ] \OR \\ % "'border-top: '" [ \ ] [ \ ] [ \ ] \OR \\ % "'border-right: '" [ \ ] [ \ ] [ \ ] \OR \\ % "'border-bottom: '" [ \ ] [ \ ] [ \ ] \OR \\ % "'border-left: '" [ \ ] [ \ ] [ \ ] \OR \\ % "'border-width: '" ( \ ) \{1,4\} \OR \\ % "'border-top-width: '" \ ) \OR \\ % "'border-right-width: '" \ ) \OR \\ % "'border-bottom-width: '" \ ) \OR \\ % "'border-left-width: '" \ ) \OR \\ % "'border-style: '" ( \ ) \{1,4\} \OR \\ % "'border-top-style: '" \ ) \OR \\ % "'border-right-style: '" \ ) \OR \\ % "'border-bottom-style: '" \ ) \OR \\ % "'border-left-style: '" \ ) \OR \\ % "'border-color: '" ( \ ) \{1,4\} \OR \\ % "'border-top-color: '" \ ) \OR \\ % "'border-right-color: '" \ ) \OR \\ % "'border-bottom-color: '" \ ) \OR \\ % "'border-left-color: '" \ ) % \item[color] "'inherit'" \OR \ \OR \\ % "'rgb('"\"','"\"','"\"')'" \OR \\ % "'hsl('"\"','"\"','"\"')'" % \end{description} % % Most of these properties are straight-forward. You should check a % CSS documentation to get more information. A very good source is the Mozilla % Developer Network. % % Here are the supported column types: % \begin{itemize} % \item "n": The most basic cell type, hbox, honoring all properties. % \item "l", "c" and "r": Same as "n" but with forced "text-align". % \item "Ml", "Mc" and "Mr": Same as column "l", "c" and "r" but enforces % "math-mode: math". The net effect is that "Mc" will create a centered % column whose contents are in non-display math mode. % \item "T"\: Same as "M"\ but enforces "math-mode: text". % \item "p{"\"}", "m"\ and "b"\: parbox cell with the % corresponding vertical alignment (\cs{vtop}, \cs{vcenter} or \cs{vbox}). % \item "*{"\"}{"\"}": same as in \pkg{array} or % \pkg{mdwtab}. % \item ">{"\"}" and "<{"\"}": % same as in \pkg{array} or \pkg{mdwtab}. % \item You can try to use constructs of \pkg{array} or \pkg{mdwtab}, but they % might alter the function of \pkg{cellprops}. Most should be fine though. % \end{itemize} % The intended usage is to use "n"-type columns and set the properties with CSS, % but \LaTeX-like columns in the preamble are often less verbose. % % Details for some properties: % % \begin{itemize} % \item "math-mode: auto" means that the cell will be in math mode in % environments \env{array}, \env{matrix}, \dots, and in text mode in % environments like \env{tabular}, \dots % \item "background-color" is only painted on the cell, and "transparent" % actually means "inherit" except that if all values encountered are % "inherit"/"transparent" no background is painted at all. That means that % (currently) you cannot paint a row in some color and rely on % transparency to have it bleed through a cell background. % \item There are no columns in the CSS object model so you have to use % "td:nth-child()" to select a column. Currently, cells spanning several % columns actually increase the child count by the number of column they % span, so that nth-child can still be used to select columns. % This is not consistent with the HTML specification of tables, but acts % as if a cell spanning multiple columns was implicitly creating % "display: none" empty cell siblings following it. % \item Any ":nth-child("$A$"n+"$B$")" or ":nth-child("$A$"n)" or % ":nth-child("$B$")" is supported, with arbitrary $A$~and~$B$, including % nothing for~$A$ (standing for~$A = 1$) or just a minus sign (standing % for~$A = -1$). % \item "vertical-align" values "baseline" and "middle" are following the CSS % specification. On the other hand, "top" (resp.~"bottom") align the top % (resp.~bottom) of the cell with the row baseline which gives a % different result than CSS if there is mixed alignment in the row. % The common case where all cells have "vertical-align: top" or % "vertical-align: bottom" behaves as expected from the CSS % specification. % % As fas as I can tell, obeying CSS in all cases would require % typesetting each row in two passes. % \item Class names are matched against the classes defined with % \cs{cellpropsclass}, which takes a space-separated list of classes. By % default, \cs{cellpropsclass} also add as a class the name of the % environment that created the tabular-like structure (that is, "tabular" % or "array" or "matrix" or similar). If you do not want that behavior, % you can use \cs{cellpropsclass*}. The class list is % redefined locally, and \pkg{cellprops} initially calls % "\cellpropsclass{}". % \item The ":where()" pseudo-class is the same as in CSS selectors level 4, % except that it does not accept spaces nor commas. Its only effect is to % neutering the specificity of the selectors within while still keeping % their matching conditions. This is mainly useful to write default yet % specific rules whose specificity would otherwise dwarf simple rules. % \end{itemize} % % \subsection{Compatibility} % % This package has been tested compatible with \pkg{diagbox}, \pkg{spreadtab}, % \pkg{collcell}. Compatibility with \pkg{longtable} has been specifically taken % care of, provided \pkg{cellprops} is loaded afterwards. Table packages that % only introduce new column types should be loaded after \pkg{mdwtab}, so either % you load \pkg{mdwtab} manually and load your package in between \pkg{mdwtab} % and \pkg{cellprops}, or you load your package after \pkg{cellprops} (provided % it doesn't overwrite the machinery). % % \subsection{TODO} % % Add a test suite with compatibility tests. Improve % the documentation, and test more \LaTeX\ table constructs and preamble column % types. % % \end{documentation} % % \begin{implementation} % % \section{\pkg{\ExplFileName} implementation} % % \begin{macrocode} %<*package> % \end{macrocode} % % \begin{macrocode} %<@@=cellprops> % \end{macrocode} % % \begin{macrocode} \ProvidesExplPackage {\ExplFileName}{\ExplFileDate}{\ExplFileVersion}{\ExplFileDescription} \RequirePackage{xparse} \RequirePackage{xcolor} \RequirePackage{etoolbox} % \end{macrocode} % % \subsection{Loading and fixing \pkg{mdwtab}} % % There is a bug in the command \cs{colpop} of \pkg{mdwtab}: instead of just % popping one name in the stack of column sets currently used, it empties it % completely because one \cs{expandafter} is missing. This is proof that not % many package authors really use this API as recommended by Mark % \textsc{Wooding}\ldots We thus load \pkg{mdwtab} and fix \cs{colpop}. % % \begin{macrocode} \RequirePackage{mdwtab} \cs_set_nopar:Npn \tab@pop #1 { \tl_set:Nx #1 { \tl_tail:N #1 } } % \end{macrocode} % % \subsection{Parsing CSS properties} % % Properties are parsed once at setting time, by expandable parsers that leave % definitions in the input stream. All these resulting definitions are saved % in a token list that will be expanded when we need the values. The goal is % to have multiple token lists for multiple contexts, yet not to do the full % parsing dance once per cell. % % \begin{variable}{\l_@@_property_value__tl} % \begin{macro}{\@@_generic_setter:nnn} % \begin{macro}{\@@_get_property:n} % \begin{macro}{\@@_get_property:nN} % We first define a generic setter which just uses % \cs{l_@@_property_value__tl} to store the value of the property. We % define getters, one that leaves the value in the stream, and one saving the % value in a token list. % % \begin{macrocode} \cs_new:Nn \@@_generic_setter:nnn { \exp_not:N \tl_set:Nn \exp_not:c { l_@@_property_value_#2_tl } {#1 {#3}} } \cs_set_nopar:Nn \@@_get_property:n { \tl_use:c { l_@@_property_value_#1_tl } } \cs_new_protected_nopar:Nn \@@_get_property:nN { \tl_if_exist:cTF { l_@@_property_value_#1_tl } { \tl_set_eq:Nc #2 { l_@@_property_value_#1_tl } }{ \tl_clear:N #2 } } % \end{macrocode} % % \end{macro} % \end{macro} % \end{macro} % \end{variable} % % \begin{macro}{\@@_property_type_:nn} % \begin{macro}{\@@_define_properties:nn} % The control sequence \cs{_@@_property_type_:nn} holds the setter for % the property \meta{name}. It can be set by the following helper: % % \begin{macrocode} \cs_new_protected:Nn \@@_define_properties:nn { \clist_map_inline:nn {#2} { \cs_set:cpn { _@@_property_type_##1:nn } {#1} } } % \end{macrocode} % % \end{macro} % \end{macro} % % \begin{macro}{\@@_use_setter:nn} % Sometimes we need to use a setter right away rather than save its action % somewhere. The following helper does that with an x-expansion. % % \begin{macrocode} \cs_new:Nn \@@_delegate_setter:nn { \use:c {_@@_property_type_#1:nn} {#1} {#2} } \cs_new_protected:Nn \@@_use_setter:nn { \use:x { \@@_delegate_setter:nn {#1} {#2} } } % \end{macrocode} % % \end{macro} % % \begin{macro}{\@@_parse_properties:nn} % Now we can parse the block of properties for a given selector. The first % argument is the token list variable which will ultimately hold the expanded % code setting internal variables from the properties. That code will be % called when recalling the computed values in a specific context. % % \begin{macrocode} \cs_new_protected:Nn \@@_parse_properties:Nn { \tl_clear:N #1 \seq_set_split:Nnn \l_tmpa_seq {;} {#2} \seq_map_inline:Nn \l_tmpa_seq { \tl_if_empty:nF {##1} { \exp_args:NNV \seq_set_split:Nnn \l_tmpb_seq \c_colon_str {##1} \int_compare:nNnT {\seq_count:N \l_tmpb_seq} = { 2 } { \seq_get_left:NN \l_tmpb_seq \l_tmpa_tl \exp_args:NNV \str_set:Nn \l_tmpa_str \l_tmpa_tl \seq_get_right:NN \l_tmpb_seq \l_tmpa_tl \cs_if_exist:cT { _@@_property_type_\l_tmpa_str :nn } { \tl_put_right:Nx #1 { \exp_args:NVV \@@_delegate_setter:nn \l_tmpa_str \l_tmpa_tl } } } } } } % \end{macrocode} % \end{macro} % % \subsection{Defining new properties} % % \subsubsection{Some helpers} % % \begin{macro}{\@@_fourval_setter:nnnnnn} % \begin{macro}{\@@_define_fourval_properties:nnnnnn} % We first define helpers to parse and define compound properties like % "padding" where you can give one to four different values and the missing % values are copied from the given ones. % % \begin{macrocode} \cs_new:Nn \@@_fourval_setter:nnnnnn { \@@_fourval_setter_aux:w {#1}{#2}{#3}{#4}#6~{\q_no_value}~{\q_no_value}~{\q_no_value}~\q_stop } \cs_new:Npn \@@_fourval_setter_aux:w #1#2#3#4#5~#6~#7~#8~#9\q_stop { \@@_delegate_setter:nn {#1} {#5} \quark_if_no_value:nTF {#6} { \@@_delegate_setter:nn {#2} {#5} \@@_delegate_setter:nn {#4} {#5} }{ \@@_delegate_setter:nn {#2} {#6} \quark_if_no_value:nTF {#8} { \@@_delegate_setter:nn {#4} {#6} }{ \@@_delegate_setter:nn {#4} {#8} } } \quark_if_no_value:nTF {#7} { \@@_delegate_setter:nn {#3} {#5} }{ \@@_delegate_setter:nn {#3} {#7} } } \cs_new_protected:Nn \@@_define_fourval_properties:nnnnnn { \@@_define_properties:nn {#1} { #3, #4, #5, #6 } \@@_define_properties:nn { \@@_fourval_setter:nnnnnn {#3}{#4}{#5}{#6} }{ #2 } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_color_setter:nn} % This macro is used to parse color definitions, either named, rgb, or hsl. % % \begin{macrocode} \tl_const:Nn \c_@@_inherit_color_tl { \q_nil } \cs_new_nopar:Nn \@@_color_setter:nn { \str_if_eq:nnTF {#2} {inherit} { \@@_generic_setter:nnn \exp_not:n {#1} {\c_@@_inherit_color_tl} }{ \str_case_e:nnF { \str_range:nnn {#2} {1} {4} } { {rgb(} { \@@_generic_setter:nnn \use:n {#1} { \exp_not:n {\color[RGB]} {\str_range:nnn {#2} {5} {-2}} }} {hsl(} { \@@_generic_setter:nnn \use:n {#1} { \exp_not:n {\color[Hsb]} {\str_range:nnn {#2} {5} {-2}} }} }{ \@@_generic_setter:nnn \exp_not:n {#1} { \color{#2} } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_bgcolor_setter:nn} % For background colors, we support "transparent" as an alias for "inherit". % % \begin{macrocode} \cs_new_nopar:Nn \@@_bgcolor_setter:nn { \str_if_eq:nnTF {#2} {transparent} { \@@_color_setter:nn {#1} {inherit} }{ \@@_color_setter:nn {#1} {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_linewidth_setter:nn} % A setter for line widths that supports common keywords: % % \begin{macrocode} \cs_new_nopar:Nn \@@_linewidth_setter:nn { \str_case:nnF {#2} { {thin} { \@@_generic_setter:nnn \exp_not:n {#1} { \fboxrule} } {medium} { \@@_generic_setter:nnn \exp_not:n {#1} { 2\fboxrule} } {thick} { \@@_generic_setter:nnn \exp_not:n {#1} { 3\fboxrule} } }{ \@@_generic_setter:nnn \exp_not:n {#1} {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_border_setter:nn } % "border" and "border-" are compound properties that can define the % width, the style and the color. As per the specification, the "border" % property always sets all four sides at the same time instead of being a % four-valued property. % % \begin{macrocode} \cs_new_nopar:Nn \@@_border_setter:nn { \@@_border_setter_aux:nw {#1}#2~{\q_no_value}~{\q_no_value}~\q_stop } \cs_new:Npn \@@_border_setter_aux:nw #1#2~#3~#4~#5\q_stop { \quark_if_no_value:nTF {#4} { \@@_border_setter_isstyle:nTF {#2} { \@@_delegate_setter:nn {#1-width} {thin} \@@_delegate_setter:nn {#1-style} {#2} \quark_if_no_value:nTF {#3} { \@@_delegate_setter:nn {#1-color} {inherit} }{ \@@_delegate_setter:nn {#1-color} {#3} } }{ \quark_if_no_value:nTF {#3} { %% One no-style value, ambiguous. Ignore the property }{ \@@_border_setter_isstyle:nTF {#3} { \@@_delegate_setter:nn {#1-width} {#2} \@@_delegate_setter:nn {#1-style} {#3} \@@_delegate_setter:nn {#1-color} {inherit} }{ \@@_delegate_setter:nn {#1-width} {#2} \@@_delegate_setter:nn {#1-style} {none} \@@_delegate_setter:nn {#1-color} {#3} } } } }{ \@@_delegate_setter:nn {#1-width} {#2} \@@_delegate_setter:nn {#1-style} {#3} \@@_delegate_setter:nn {#1-color} {#4} } } \cs_new:Npn \@@_border_setter_isstyle:nTF #1 { \str_case:nnTF {#1} { {none}{} {hidden}{} {dotted}{} {dashed}{} {solid}{} {double}{} {groove}{} {ridge}{} {inset}{} {outset}{} } } % \end{macrocode} % \end{macro} % % \subsubsection{Actual definitions of properties} % % First some simple-valued properties where we just store the value % unexpanded. % % \begin{macrocode} \@@_define_properties:nn { \@@_generic_setter:nnn \exp_not:n }{ min-height, min-depth, min-width, } % \end{macrocode} % % "padding" is a compound property for "padding-" which store their % value unexpanded. % % \begin{macrocode} \@@_define_fourval_properties:nnnnnn { \@@_generic_setter:nnn \exp_not:n } {padding} {padding-top}{padding-right}{padding-bottom}{padding-left} % \end{macrocode} % % Simple-valued properties that store a str value. % % \begin{macrocode} \@@_define_properties:nn { \@@_generic_setter:nnn \tl_to_str:n }{ text-align, vertical-align, math-mode, } % \end{macrocode} % % Some simple-valued color properties, using the dedicated parser. % % \begin{macrocode} \@@_define_properties:nn { \@@_color_setter:nn }{ color, } \@@_define_properties:nn { \@@_bgcolor_setter:nn }{ background-color, } % \end{macrocode} % % A compound property whose individual sides use the linewidth setter for % keyword recognition. % % \begin{macrocode} \@@_define_fourval_properties:nnnnnn { \@@_linewidth_setter:nn } {border-width} {border-top-width}{border-right-width} {border-bottom-width}{border-left-width} % \end{macrocode} % % A compound property whose individual sides are str values. They could be % checked against the list of valid values, but any non-existing one will be % ignored anyway due to the way they are implemented. % % \begin{macrocode} \@@_define_fourval_properties:nnnnnn { \@@_generic_setter:nnn \tl_to_str:n } {border-style} {border-top-style}{border-right-style} {border-bottom-style}{border-left-style} % \end{macrocode} % % A compound property whose individual sides are colors. % % \begin{macrocode} \@@_define_fourval_properties:nnnnnn { \@@_color_setter:nn } {border-color} {border-top-color}{border-right-color} {border-bottom-color}{border-left-color} % \end{macrocode} % % The five border-specific compound properties are defined here. % % \begin{macrocode} \@@_define_properties:nn { \@@_border_setter:nn }{ border, border-top, border-right, border-bottom, border-left } % \end{macrocode} % % \subsection{Parsing a CSS stylesheet} % % \begin{macrocode} \NewDocumentCommand \cellprops { m } { \@@_parse_css:n {#1} } \cs_new_protected:Nn \@@_parse_css:n { \@@_parse_css:w #1 \q_mark {\q_nil} \q_stop % \end{macrocode} % % Ensure the already seen specificities is in order without duplicates. % % \begin{macrocode} \seq_remove_duplicates:N \l_@@_specificities_seq \seq_sort:Nn \l_@@_specificities_seq { \int_compare:nNnTF { ##1 } > { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } % \end{macrocode} % % Grab the content up to the first opening brace. That content will be the % comma-separated selector list, and the braced content is a block of % properties. We can loop until there is no such block remaining. % % \begin{macrocode} \tl_new:N \l_@@_parse_properties_tl \NewDocumentCommand \@@_parse_css:w { lmu{\q_stop} } { \quark_if_nil:nF {#2} { \@@_parse_properties:Nn \l_@@_parse_properties_tl {#2} \clist_map_inline:nn {#1} { \tl_if_empty:nF {##1} { \@@_parse_css_addprops:n {##1} } } \@@_parse_css:w #3 \q_stop } } % \end{macrocode} % % Some pseudo-classes generate conditional code for the properties to be % applied. Check if such code exists, and wrap the parsed property setters % in a \cs{bool_if:nT}. % % \begin{macrocode} \tl_new:N \l_@@_current_selector_tl \tl_new:N \l_@@_current_selector_check_tl \cs_new_protected:Nn \@@_parse_css_addprops:n { \@@_parse_selector:n {#1} \tl_if_empty:NF \l_@@_current_selector_tl { \tl_set:Nx \l_tmpa_tl { l_@@_property_group_\l_@@_current_selector_tl _tl } \tl_if_exist:cF { \l_tmpa_tl } { \tl_clear:c { \l_tmpa_tl } } \tl_if_empty:NTF \l_@@_current_selector_check_tl { \tl_put_right:cV { \l_tmpa_tl } \l_@@_parse_properties_tl }{ \tl_put_right:cx { \l_tmpa_tl } { \exp_not:N \bool_if:nT { \exp_not:V \l_@@_current_selector_check_tl }{ \exp_not:V \l_@@_parse_properties_tl } } } } } % \end{macrocode} % % Here we parse a selector. These are naturally space-separated, but we first % need to detect and normalize constructs like ":nth-child(argument)". We % replace them by ":nth-child{argument}" where the braces will procect any % space that can legitimately occur within "argument". % % \begin{macrocode} \cs_new_protected:Nn \@@_parse_selector_sanitize:n { \exp_args:Nx \@@_parse_selector_sanitize_aux:n { \tl_to_str:n{#1} } } \cs_new_protected:Nn \@@_parse_selector_sanitize_aux:n { \cs_set:Npn \@@_parse_selector_sanitize:w ##1:#1(##2)##3\q_stop { \quark_if_nil:nTF {##3} { ##1 }{ ##1:#1{##2}\@@_parse_selector_sanitize:w ##3\q_stop } } \tl_set:Nx \l_@@_current_selector_tl { \exp_last_unbraced:NV \@@_parse_selector_sanitize:w \l_@@_current_selector_tl :#1()\q_nil\q_stop } } % \end{macrocode} % % Now that we can sanitize pseudo-classes, parsing the selector is safe. The % constructs to protect are ":nth-child()" and ":where()" since other supported % pseudo-classes have no argument. % % \begin{macrocode} \seq_new:N \l_@@_current_selector_seq \seq_new:N \l_@@_pseudoclasses_seq \tl_new:N \l_@@_current_element_tl \tl_new:N \l_@@_current_tableclass_tl \int_new:N \l_@@_current_level_int \int_new:N \l_@@_current_specificity_int \seq_new:N \l_@@_specificities_seq \cs_new_protected:Nn \@@_parse_selector:n { \tl_set:Nx \l_@@_current_selector_tl { \tl_to_str:n {#1} } % \end{macrocode} % % The sanitize code is more readable with Expl category colon, so replace it % now, instead of defining the method with expand or lccode tricks. % % \begin{macrocode} \exp_args:NNV \tl_replace_all:Nnn \l_@@_current_selector_tl \c_colon_str {:} \@@_parse_selector_sanitize:n {nth-child} \@@_parse_selector_sanitize:n {where} \seq_set_split:NnV \l_@@_current_selector_seq {~} \l_@@_current_selector_tl \tl_clear:N \l_@@_current_selector_tl \tl_clear:N \l_@@_current_selector_check_tl \tl_clear:N \l_@@_current_tableclass_tl \int_set:Nn \l_@@_current_level_int {-1} \int_zero:N \l_@@_current_specificity_int \seq_map_inline:Nn \l_@@_current_selector_seq { \tl_clear:N \l_@@_current_element_tl \@@_parse_simple_selector:n {##1} } % \end{macrocode} % % Remember the current selector as "~". % % \begin{macrocode} \tl_if_empty:NF \l_@@_current_element_tl { \tl_if_empty:NF \l_@@_current_tableclass_tl { \tl_set:Nx \l_@@_current_tableclass_tl { l_@@_active_classes_\l_@@_current_tableclass_tl _bool } \bool_if_exist:cF { \l_@@_current_tableclass_tl } { \bool_new:c { \l_@@_current_tableclass_tl } } \@@_add_check:x { \exp_not:n { \bool_if_p:N } { \exp_not:c { \l_@@_current_tableclass_tl } } } } \tl_set:Nx \l_@@_current_selector_tl { \exp_not:V \l_@@_current_element_tl \exp_not:n {~} \int_use:N \l_@@_current_specificity_int } \seq_put_right:Nx \l_@@_specificities_seq { \int_use:N \l_@@_current_specificity_int } } } \tl_new:N \l_@@_maybe_element_tl \cs_new_protected:Nn \@@_parse_simple_selector:n { % \end{macrocode} % % First we replace "." with ":." so that class selectors can be handled with % the same code as pseudo-classes. Then we split the current selector item on % ":" to get the base element and the pseudo-classes. % % \begin{macrocode} \tl_set:Nn \l_tmpa_tl { #1 } \tl_replace_all:Nnn \l_tmpa_tl {.} {:.} \seq_set_split:NnV \l_@@_pseudoclasses_seq {:} \l_tmpa_tl \seq_pop_left:NN \l_@@_pseudoclasses_seq \l_@@_maybe_element_tl % \end{macrocode} % % Known type selectors increase the specificity by one. % % \begin{macrocode} \str_case:VnTF \l_@@_maybe_element_tl { { table } { \int_set:Nn \l_tmpa_int { 1 } } { tr } { \int_set:Nn \l_tmpa_int { 2 } } { td } { \int_set:Nn \l_tmpa_int { 3 } } { p } { \int_set:Nn \l_tmpa_int { 4 } } }{ \int_add:Nn \l_@@_current_specificity_int { 1 } }{ % \end{macrocode} % % An empty type selector is accepted to account for ":where()" pseudo-classes. % They will be handled in the loop below, but of course there should be a type % selector eventually. Map that case to a big positive next level so that we can % detect that and keep the current level as-is. % % Unknown type selectors are mapped to big negative levels % so that the descendant check will refuse them, unless we are at the very % first type selector in which case we map the deprecated environment % selector to a class selector. % % \begin{macrocode} \tl_if_empty:NTF \l_@@_maybe_element_tl { \int_set:Nn \l_tmpa_int { 10 } }{ \int_compare:nNnTF \l_@@_current_level_int = { -1 } { \tl_set_eq:NN \l_@@_current_tableclass_tl \l_@@_maybe_element_tl \tl_set:Nn \l_@@_maybe_element_tl { table } \int_set:Nn \l_tmpa_int { 1 } \int_add:Nn \l_@@_current_specificity_int { 11 } }{ \int_set:Nn \l_tmpa_int { -10 } } } } % \end{macrocode} % % If no type selector has been found yet, record the one we may have found % now. Else, refuse to have two in the same selector. % % \begin{macrocode} \tl_if_empty:NTF \l_@@_current_element_tl { \tl_set_eq:NN \l_@@_current_element_tl \l_@@_maybe_element_tl }{ \tl_if_empty:NF \l_@@_maybe_element_tl { \int_set:Nn \l_tmpa_int { -10 } } } % \end{macrocode} % % If the detected element is a descendent of the previous one, the selector % can match; count the element selector specificity and parse its % pseudo-classes. If not, the whole selector cannot match and we discard it % early. % % \begin{macrocode} \int_compare:nNnTF \l_tmpa_int > \l_@@_current_level_int { \int_compare:nNnT \l_tmpa_int < 10 { \int_set_eq:NN \l_@@_current_level_int \l_tmpa_int } \seq_map_inline:Nn \l_@@_pseudoclasses_seq { \@@_parse_pseudoclass:w ##1{}\q_stop } }{ \tl_clear:N \l_@@_current_element_tl } % \end{macrocode} % % At the end of the selector parsing, the current element should not be empty. % % \begin{macrocode} \tl_if_empty:NT \l_@@_current_element_tl { \seq_map_break: } } % \end{macrocode} % % The first argument is the complete pseudo-class up to the opening argument % brace (if any), and the second argument is the braced content (if any). % The third argument gobbles any trailing garbage. % % If the pseudo-class starts with a "." this is a class selector. Else, this % is an unknown pseudo-class and we reject the complete selector. % % \begin{macrocode} \NewDocumentCommand \@@_parse_pseudoclass:w { lmu{\q_stop} } { \str_case:nnF { #1 } { {first-child} { \@@_parse_selector_nth:n {1} } {nth-child} { \@@_parse_selector_nth:n {#2} } {where} { \@@_parse_where:n {#2} } }{ \str_if_eq:eeTF { \str_head:n { #1 } } {.} { \tl_set:Nx \l_@@_current_tableclass_tl { \str_tail:n { #1 } } \int_add:Nn \l_@@_current_specificity_int { 10 } }{ \tl_clear:N \l_@@_current_element_tl \seq_map_break:n { \seq_map_break: } } } } \str_const:Nn \c_@@_parse_n_str {n} \int_new:N \l_@@_nth_coeff_int \int_new:N \l_@@_nth_offset_int \cs_new_protected:Nn \@@_parse_selector_nth:n { % \end{macrocode} % % Count the pseudo-class in the specificity. % % \begin{macrocode} \int_add:Nn \l_@@_current_specificity_int { 10 } % \end{macrocode} % % Now parse the nth-child argument: % % \begin{macrocode} \str_case:nnF {#1} { {even} { \str_set:Nn \l_tmpa_str {2n} } {odd} { \str_set:Nn \l_tmpa_str {2n+1} } }{ \str_set:Nn \l_tmpa_str {#1} } \exp_args:NNV \seq_set_split:NnV \l_tmpa_seq \c_@@_parse_n_str \l_tmpa_str \seq_pop_right:NN \l_tmpa_seq \l_tmpa_tl \tl_if_empty:NTF \l_tmpa_tl { \int_zero:N \l_@@_nth_offset_int }{ \int_set:Nn \l_@@_nth_offset_int { \l_tmpa_tl } } \seq_get_left:NNTF \l_tmpa_seq \l_tmpa_tl { \tl_if_empty:NTF \l_tmpa_tl { \int_set:Nn \l_@@_nth_coeff_int {1} }{ \exp_args:NV \tl_if_eq:nnTF \l_tmpa_tl {-} { \int_set:Nn \l_@@_nth_coeff_int {-1} }{ \int_set:Nn \l_@@_nth_coeff_int { \l_tmpa_tl } } } }{ \int_zero:N \l_@@_nth_coeff_int } % \end{macrocode} % % At last, generate the condition code. % % \begin{macrocode} \str_case:Vn \l_@@_current_element_tl { {tr} { \@@_generate_check_nth:n {\g_@@_row_int} } {td} { \@@_generate_check_nth:n {\g_@@_col_int} } } } \cs_new_protected_nopar:Nn \@@_generate_check_nth:n { \int_compare:nNnTF \l_@@_nth_coeff_int = { 0 } { \@@_add_check:x { \exp_not:n { \int_compare_p:nNn #1 = } { \exp_not:V \l_@@_nth_offset_int } } }{ \tl_set:Nx \l_tmpb_tl { { \exp_not:n { #1 - } \exp_not:V \l_@@_nth_offset_int }{ \exp_not:V \l_@@_nth_coeff_int } } \@@_add_check:x { \exp_not:N \bool_lazy_and_p:nn { \exp_not:n { \int_compare_p:nNn 0 = } { \exp_not:N \int_mod:nn \exp_not:V \l_tmpb_tl } }{ \exp_not:n { \int_compare_p:nNn 0 < } { \exp_not:N \int_div_truncate:nn \exp_not:V \l_tmpb_tl \exp_not:n { + 1 } } } } } } \cs_new_protected:Nn \@@_parse_where:n { \use:x { \exp_not:n { \@@_parse_simple_selector:n { #1 } \int_set:Nn \l_@@_current_specificity_int } { \int_use:N \l_@@_current_specificity_int } } } \cs_new_protected:Nn \@@_add_check:n { \tl_if_empty:NTF \l_@@_current_selector_check_tl { \tl_set:Nn \l_@@_current_selector_check_tl { #1 } }{ \tl_set:Nx \l_@@_current_selector_check_tl { \exp_not:N \bool_lazy_and_p:nn { \exp_not:V \l_@@_current_selector_check_tl }{ \exp_not:n { #1 } } } } } \cs_generate_variant:Nn \@@_add_check:n {x} \seq_new:N \l_@@_classes_seq \NewDocumentCommand \cellpropsclass { sm } { \seq_set_split:Nnn \l_@@_classes_seq {~} { #2 } \IfBooleanF {#1} { \seq_put_right:Nn \l_@@_classes_seq { \@currenvir } } } \cellpropsclass{} \cs_set_protected:Nn \@@_recall_properties:n { \seq_map_inline:Nn \l_@@_specificities_seq { \tl_if_exist:cT { l_@@_property_group_#1~##1_tl } { \tl_use:c { l_@@_property_group_#1~##1_tl } } } } \dim_new:N \l_@@_colsep_dim \dim_new:N \l_@@_strut_ht_dim \dim_new:N \l_@@_strut_dp_dim \ExplSyntaxOff \cellprops{ :where(td) { padding: 0pt \csname l_@@_colsep_dim\endcsname; min-height: \csname l_@@_strut_ht_dim\endcsname; min-depth: \csname l_@@_strut_dp_dim\endcsname; min-width: 0pt; text-align: left; vertical-align: baseline; math-mode: auto; color: inherit; background-color: transparent; border: thin none inherit; } :where(tr) { color: inherit; background-color: transparent; } :where(table) { padding: 0pt; % No change at load time color: inherit; background-color: transparent; } } \ExplSyntaxOn \int_new:N \g_@@_row_int \int_new:N \g_@@_col_int \bool_new:N \g_@@_inrow_bool \bool_gset_false:N \g_@@_inrow_bool \box_new:N \l_@@_cell_box \skip_new:N \l_@@_left_skip \skip_new:N \l_@@_right_skip \dim_new:N \g_@@_ht_dim \dim_new:N \g_@@_dp_dim \tl_new:N \g_@@_borders_tl \tl_new:N \l_@@_restore_tl \dim_new:N \l_@@_tablepadding_top_dim \dim_new:N \l_@@_tablepadding_bottom_dim \tl_new:N \l_@@_color_tl \tl_new:N \l_@@_bgcolor_tl \seq_new:N \l_@@_classes_at_start_seq \cs_new_protected:Nn \@@_array_init: { \tl_set:Nx \l_@@_restore_tl { \bool_if:NTF \g_@@_inrow_bool { \exp_not:n {\bool_gset_true:N \g_@@_inrow_bool} }{ \exp_not:n {\bool_gset_false:N \g_@@_inrow_bool} } \exp_not:n { \int_gset:Nn \g_@@_row_int } { \int_use:N \g_@@_row_int } \exp_not:n { \int_gset:Nn \g_@@_col_int } { \int_use:N \g_@@_col_int } \exp_not:n { \dim_gset:Nn \g_@@_ht_dim } { \dim_use:N \g_@@_ht_dim } \exp_not:n { \dim_gset:Nn \g_@@_dp_dim } { \dim_use:N \g_@@_dp_dim } \exp_not:n { \tl_gset:Nn \g_@@_borders_tl } { \exp_not:V \g_@@_borders_tl } } % \end{macrocode} % % Unset all previous active classes, and set current ones as active. % % \begin{macrocode} \seq_map_inline:Nn \l_@@_classes_at_start_seq { \bool_set_false:c { l_@@_active_classes_##1_bool } } \seq_set_eq:NN \l_@@_classes_at_start_seq \l_@@_classes_seq \seq_map_inline:Nn \l_@@_classes_at_start_seq { \bool_set_true:c { l_@@_active_classes_##1_bool } } % \end{macrocode} % % To count rows and columns. % % \begin{macrocode} \int_gzero:N \g_@@_row_int \bool_gset_false:N \g_@@_inrow_bool \tl_gclear:N \g_@@_borders_tl \cs_set_eq:NN \@@_orig_tab@readpreamble:n \tab@readpreamble \cs_set_eq:NN \tab@readpreamble \@@_readpreamble:n % \end{macrocode} % % Zero \cs{col@sep} but remember its value for the default padding. % % \begin{macrocode} \dim_set_eq:NN \l_@@_colsep_dim \col@sep \dim_zero:N \col@sep % \end{macrocode} % % Also ignore \cs{*extrasep} dimensions that are not part of cellprop % interface and should be replaced by CSS equivalents. % % \begin{macrocode} \dim_zero:N \tab@extrasep \group_begin: \@@_recall_properties:n {table} \dim_gset:Nn \g_tmpa_dim { \@@_get_property:n {padding-top} } \dim_gset:Nn \g_tmpb_dim { \@@_get_property:n {padding-bottom} } \@@_update_colors: \tl_gset_eq:NN \g_tmpa_tl \l_@@_color_tl \tl_gset_eq:NN \g_tmpb_tl \l_@@_bgcolor_tl \group_end: \dim_set_eq:NN \l_@@_tablepadding_top_dim \g_tmpa_dim \dim_set_eq:NN \l_@@_tablepadding_bottom_dim \g_tmpb_dim \tl_set_eq:NN \l_@@_color_tl \g_tmpa_tl \tl_set_eq:NN \l_@@_bgcolor_tl \g_tmpb_tl \dim_set:Nn \l_@@_strut_ht_dim { \box_ht:N \@arstrutbox } \dim_set:Nn \l_@@_strut_dp_dim { \box_dp:N \@arstrutbox } \box_clear:N \@arstrutbox } \cs_set_nopar:Nn \@@_array_startcontent: { \hlx{s[\l_@@_tablepadding_top_dim]} } \cs_new_protected_nopar:Nn \@@_maybe_startrow: { \bool_if:NF \g_@@_inrow_bool { \bool_gset_true:N \g_@@_inrow_bool \int_gincr:N \g_@@_row_int \int_gset_eq:NN \g_@@_col_int \c_one_int \dim_gzero:N \g_@@_ht_dim \dim_gzero:N \g_@@_dp_dim } } \cs_new_protected_nopar:Nn \@@_maybe_endrow: { \bool_if:NT \g_@@_inrow_bool { \@@_every_cell_end: \bool_gset_false:N \g_@@_inrow_bool } } \cs_new_protected_nopar:Nn \@@_every_cell_end: { \int_gincr:N \g_@@_col_int } \cs_set_protected_nopar:Nn \@@_readpreamble:n { \cs_set_eq:NN \tab@readpreamble \@@_orig_tab@readpreamble:n % \end{macrocode} % % \cs{tab@multicol} is inserted at the beginning of a each row, and by % \cs{multicolumn} after its \cs{omit}. We use it to ensure that the row is % initialized correctly however it starts (normally or with a % \cs{multicolumn}). % % \cs{tab@tabtext} is inserted at the end of every cell but the last one, so % we should ensure that its effect is applied at the end of the row; % \cs{@@_maybe_endrow} will take care of that. % % \begin{macrocode} \tl_put_left:Nn \tab@multicol {\@@_maybe_startrow:} \tl_put_left:Nn \tab@tabtext {\@@_every_cell_end:} \tab@readpreamble{#1} \exp_args:Nx \tab@preamble { \the\tab@preamble \exp_not:N\@@_maybe_endrow: } } % \end{macrocode} % % The color inheritance is handled with \cs{l_@@_inherit_color_tl}, % \cs{l_@@_color_tl} and \cs{l_@@_bgcolor_tl}. The role of % \cs{@@_update_color:Nn} is to set the inherit fallback to the already % existing value of \verb|#1| then set \verb|#1| to the CSS value, which can be % the inherit variable. % % \begin{macrocode} \cs_new_protected_nopar:Nn \@@_update_color:Nn { \@@_get_property:nN {#2} \l_tmpa_tl \exp_args:NV \tl_if_eq:NNF \l_tmpa_tl \c_@@_inherit_color_tl { \tl_set_eq:NN #1 \l_tmpa_tl } } \cs_new_protected_nopar:Nn \@@_update_colors: { \@@_update_color:Nn \l_@@_color_tl {color} \@@_update_color:Nn \l_@@_bgcolor_tl {background-color} } % \end{macrocode} % % Patch the \cs{@array}, \cs{LT@array}, \cs{@mkpream}, \cs{endarray} and % \cs{endlongtable} commands, so that we can properly setup our line and column % counting system. This is the most brittle part of \pkg{cellprops}, and subject % to compatibility problems with other packages that patch those (\pkg{hyperref} % in particular). % % \begin{macrocode} \AtEndPreamble{% \cs_set_eq:NN \@@_orig_array:w \@array \cs_set_protected_nopar:Npn \@array[#1]#2 { \@@_array_init: \@@_orig_array:w [#1]{#2} \@@_array_startcontent: } \cs_set_eq:NN \@@_orig_LTmkpream:n \@mkpream \cs_set_protected_nopar:Npn \@mkpream#1 { \group_end: \@@_array_init: \group_begin: \@@_orig_LTmkpream:n {#1} } \cs_set_eq:NN \@@_orig_LTarray:w \LT@array \cs_set_protected_nopar:Npn \LT@array [#1]#2 { \@@_orig_LTarray:w [#1]{#2} \@@_array_startcontent: } \cs_new_nopar:Nn \@@_end_array:n { \tl_if_empty:NF \g_@@_borders_tl { \\ } \crcr \hlx{s[\l_@@_tablepadding_bottom_dim]} #1 \tl_use:N \l_@@_restore_tl } \cs_set_eq:NN \@@_orig_endarray: \endarray \cs_set_nopar:Npn \endarray { \@@_end_array:n { \@@_orig_endarray: } } \cs_set_eq:NN \endtabular \endarray \cs_set_eq:cN {endtabular*} \endarray \cs_set_eq:NN \@@_orig_endLT: \endlongtable \cs_set_nopar:Npn \endlongtable { \@@_end_array:n { \@@_orig_endLT: } } \cs_new_protected_nopar:Nn \@@_cr:n { \@@_maybe_endrow: \tl_if_empty:NF \g_@@_borders_tl { \cr \noalign{\nobreak} \tl_use:N \g_@@_borders_tl \tl_gclear:N \g_@@_borders_tl } \cr \@@_fix_valign_end:n {#1} \use_none:n } \cs_set_protected_nopar:Npn \tab@tabcr #1#2 { \@@_cr:n {#2} } \cs_set_protected_nopar:Npn \@xargarraycr #1 { \@@_cr:n {#1} } \cs_set_protected_nopar:Npn \@yargarraycr #1 { \@@_cr:n {#1} } \tl_if_exist:NT \LT@echunk { \tl_put_left:Nn \LT@echunk { \tl_if_empty:NF \g_@@_borders_tl { \\ } } } \cs_set_eq:NN \@@_orig_multicolumn:w \multicolumn \cs_set:Npn \multicolumn#1#2#3 { \@@_orig_multicolumn:w {#1}{#2}{#3} \int_gadd:Nn \g_@@_col_int {#1} \tl_gput_right:Nx \g_@@_borders_tl { \prg_replicate:nn {#1 - 1} {\span\omit} } \ignorespaces } } \cs_new_nopar:Nn \@@_fix_valign_end:n { \noalign{ \dim_set:Nn \l_tmpa_dim {#1} \skip_vertical:n {\l_tmpa_dim} \exp_args:NV \tl_if_eq:nnTF \tab@hlstate {b} { \dim_gadd:Nn \tab@endheight { \g_@@_dp_dim + \l_tmpa_dim } }{ \int_compare:nNnT \g_@@_row_int = \c_one_int { \dim_gadd:Nn \tab@endheight { \g_@@_ht_dim } } } } } % \end{macrocode} % % Reset \cs{firsthline} and \cs{lasthline} to \cs{hline} because the version % from \pkg{array} which might be loaded already will mess up the spacing and is % unneeded anyway. % % \begin{macrocode} \cs_set_eq:NN \firsthline \hline \cs_set_eq:NN \lasthline \hline \colpush{tabular} \coldef n{\tabcoltype{ \@@_begincell:n{} }{ \@@_endcell: }} \coldef l{\tabcoltype{ \@@_begincell:n {\@@_use_setter:nn {text-align} {left}} }{ \@@_endcell: }} \coldef c{\tabcoltype{ \@@_begincell:n {\@@_use_setter:nn {text-align} {center}} }{ \@@_endcell: }} \coldef r{\tabcoltype{ \@@_begincell:n {\@@_use_setter:nn {text-align} {right}} }{ \@@_endcell: }} \coldef M#1{\@@_MTcol:nn {math}{#1}} \coldef T#1{\@@_MTcol:nn {text}{#1}} \cs_new_protected_nopar:Nn \@@_MTcol:nn { % TODO: error if align not l, c, or r \exp_args:Nx \tabcoltype { \exp_not:N \@@_begincell:n { \exp_not:n {\@@_use_setter:nn {math-mode} {#1} } \exp_not:n {\@@_use_setter:nn {text-align}} { \str_case:nn {#2} { {l} {left} {c} {center} {r} {right} } } } }{ \@@_endcell: } } \coldef p#1{\tabcoltype{ \@@_begin_par_cell:nn \vtop {#1} }{ \@@_end_par_cell:n {} }} \coldef m#1{\tabcoltype{ \@@_begin_par_cell:nn {\c_math_toggle_token\vcenter} {#1} }{ \@@_end_par_cell:n{\c_math_toggle_token} }} \coldef b#1{\tabcoltype{ \@@_begin_par_cell:nn \vbox {#1} }{ \@@_end_par_cell:n {} }} \colpop \cs_new_protected_nopar:Nn \@@_begincell:n { \@@_begin_raw_cell:n { #1 \hbox_set:Nw \l_@@_cell_box \str_case_e:nnF {\@@_get_property:n {math-mode}} { { text } { \tab@btext } { math } { \tab@bmaths } }{% any other treated as |auto| \tab@bgroup } } } \cs_new_protected_nopar:Nn \@@_endcell: { \str_case_e:nnF {\@@_get_property:n {math-mode}} { { text } { \tab@etext } { math } { \tab@emaths } }{% any other treated as |auto| \tab@egroup } \hbox_set_end: \@@_end_raw_cell: } \cs_new_protected_nopar:Nn \@@_begin_par_cell:nn { \savenotes \@@_begin_raw_cell:n{ \hbox_set:Nw \l_@@_cell_box #1 \bgroup \hsize#2\relax \@arrayparboxrestore \global\@minipagetrue \everypar{ \global\@minipagefalse \everypar{} } \@@_recall_properties:n {p} } } \cs_new_protected_nopar:Nn \@@_end_par_cell:n { \ifhmode\@maybe@unskip\par\fi \unskip \egroup #1 \hbox_set_end: \@@_end_raw_cell: \spewnotes } \cs_new_protected_nopar:Nn \@@_begin_raw_cell:n { \group_begin: \@@_recall_properties:n {tr} \@@_update_colors: \@@_recall_properties:n {td} \@@_update_colors: % Additional init code #1 % Install the cell color \@@_update_colors: \tl_use:N \l_@@_color_tl } \cs_new_protected_nopar:Nn \@@_make_solid_hborder:nnn { \group_begin: \hbox_set_to_wd:Nnn \l_tmpa_box {1pt} { \hss \hbox:n { #3 % install color \vrule height~\dim_eval:n{#1+#2} ~depth~-\dim_eval:n{#2} ~width~3pt } \hss } \box_set_ht:Nn \l_tmpa_box { \c_zero_dim } \box_set_dp:Nn \l_tmpa_box { \c_zero_dim } \kern 1pt \box_use:N \l_tmpa_box \xleaders \box_use:N \l_tmpa_box \skip_horizontal:n {-4pt~plus~1fil} \box_use:N \l_tmpa_box \kern 1pt \skip_horizontal:n {0pt~plus~-1fil} \group_end: } \cs_new_protected_nopar:Nn \@@_make_solid_vborder:nnn { \group_begin: \hbox_set_to_wd:Nnn \l_tmpa_box {0pt} { \hbox:n { #3 % install color \vrule height~\dim_eval:n{#2}~width~\dim_eval:n{#1} } \hss } \box_set_ht:Nn \l_tmpa_box { \c_zero_dim } \box_set_dp:Nn \l_tmpa_box { \c_zero_dim } \box_use:N \l_tmpa_box \group_end: } \clist_map_inline:nn { dotted, dashed, solid, double, groove, ridge, inset, outset }{ \cs_set_eq:cN {@@_make_#1_hborder:nnn} \@@_make_solid_hborder:nnn \cs_set_eq:cN {@@_make_#1_vborder:nnn} \@@_make_solid_vborder:nnn } \dim_new:N \l_@@_border_width_dim \str_new:N \l_@@_border_style_str \tl_new:N \l_@@_border_color_tl \cs_new_protected_nopar:Nn \@@_get_border_info:n { \dim_set:Nn \l_@@_border_width_dim {\@@_get_property:n {border-#1-width}} \@@_get_property:nN {border-#1-style} \l_tmpa_tl \exp_args:NNV \str_set:Nn \l_@@_border_style_str \l_tmpa_tl \tl_clear:N \l_@@_border_color_tl \cs_if_exist:cTF {@@_make_\l_@@_border_style_str _hborder:nnn} { \@@_update_color:Nn \l_@@_border_color_tl {border-#1-color} }{ \dim_zero:N \l_@@_border_width_dim } } \cs_new_protected_nopar:Npn \@@_make_hborder:nnnn #1 { \use:c { @@_make_#1_hborder:nnn } } \cs_new_protected_nopar:Npn \@@_make_vborder:nnnn #1 { \use:c { @@_make_#1_vborder:nnn } } \cs_new_protected_nopar:Nn \@@_end_raw_cell: { % Here \l_@@_cell_box must contain the contents of the cell % % Prepare the borders token list \int_compare:nNnT \g_@@_col_int = 1 { \tl_gclear:N \g_@@_borders_tl } \tl_gput_right:Nx \g_@@_borders_tl { \tl_if_empty:NF \g_@@_borders_tl { \exp_not:n {&} } \exp_not:n { \omit \kern \c_zero_dim } } % Handle min-height, min-depth and vertical-align % wrong values are treated as |baseline|. \box_set_ht:Nn \l_@@_cell_box { \dim_max:nn {\box_ht:N \l_@@_cell_box} {\@@_get_property:n {min-height}} } \box_set_dp:Nn \l_@@_cell_box { \dim_max:nn {\box_dp:N \l_@@_cell_box} {\@@_get_property:n {min-depth}} } \str_case_e:nn {\@@_get_property:n {vertical-align}} { { top } { \hbox_set:Nn \l_@@_cell_box { \vbox_top:n { \kern 0pt\relax \box_use_drop:N \l_@@_cell_box } } } { bottom } { \hbox_set:Nn \l_@@_cell_box { \vbox:n { \box_use_drop:N \l_@@_cell_box \kern 0pt\relax } } } { middle } { \hbox_set:Nn \l_@@_cell_box { \dim_set:Nn \l_tmpa_dim { (\box_dp:N \l_@@_cell_box - \box_ht:N \l_@@_cell_box + 1ex) / 2 } \raisebox{\l_tmpa_dim}{\box_use_drop:N \l_@@_cell_box} } } } % Handle padding-top and border-top \@@_get_border_info:n {top} \box_set_ht:Nn \l_@@_cell_box { \box_ht:N \l_@@_cell_box + (\@@_get_property:n {padding-top}) + \l_@@_border_width_dim } \dim_compare:nNnT \l_@@_border_width_dim > \c_zero_dim { \tl_gput_right:Nx \g_@@_borders_tl { \exp_not:N \@@_make_hborder:nnnn { \exp_not:V \l_@@_border_style_str } { \dim_use:N \l_@@_border_width_dim } { \exp_not:n { \g_@@_dp_dim + \g_@@_ht_dim - } \dim_use:N \l_@@_border_width_dim } { \exp_not:V \l_@@_border_color_tl } } } % Handle padding-bottom and border-bottom \@@_get_border_info:n {bottom} \box_set_dp:Nn \l_@@_cell_box { \box_dp:N \l_@@_cell_box + (\@@_get_property:n {padding-bottom}) + \l_@@_border_width_dim } \dim_compare:nNnT \l_@@_border_width_dim > \c_zero_dim { \tl_gput_right:Nx \g_@@_borders_tl { \exp_not:N \@@_make_hborder:nnnn { \exp_not:V \l_@@_border_style_str } { \dim_use:N \l_@@_border_width_dim } { \exp_not:n { 0pt } } { \exp_not:V \l_@@_border_color_tl } } } % To fix vertical alignment later \dim_gset:Nn \g_@@_ht_dim { \dim_max:nn {\g_@@_ht_dim} {\box_ht:N \l_@@_cell_box} } \dim_gset:Nn \g_@@_dp_dim { \dim_max:nn {\g_@@_dp_dim} {\box_dp:N \l_@@_cell_box} } % Handle padding-left and border-left \@@_get_border_info:n {left} \skip_set:Nn \l_@@_left_skip {\@@_get_property:n {padding-left} + \l_@@_border_width_dim} \dim_compare:nNnT \l_@@_border_width_dim > \c_zero_dim { \tl_gput_right:Nx \g_@@_borders_tl { \exp_not:N \@@_make_vborder:nnnn { \exp_not:V \l_@@_border_style_str } { \dim_use:N \l_@@_border_width_dim } { \exp_not:n { \g_@@_dp_dim + \g_@@_ht_dim } } { \exp_not:V \l_@@_border_color_tl } } } \tl_gput_right:Nx \g_@@_borders_tl { \exp_not:n { \skip_horizontal:n {0pt~plus~1fil} \kern \c_zero_dim } } \@@_get_border_info:n {right} \skip_set:Nn \l_@@_right_skip {\@@_get_property:n {padding-right} + \l_@@_border_width_dim} \dim_compare:nNnT \l_@@_border_width_dim > \c_zero_dim { \tl_gput_right:Nx \g_@@_borders_tl { \exp_not:N \skip_horizontal:n { - \dim_use:N \l_@@_border_width_dim } \exp_not:N \@@_make_vborder:nnnn { \exp_not:V \l_@@_border_style_str } { \dim_use:N \l_@@_border_width_dim } { \exp_not:n { \g_@@_dp_dim + \g_@@_ht_dim } } { \exp_not:V \l_@@_border_color_tl } \exp_not:N \skip_horizontal:n { \dim_use:N \l_@@_border_width_dim } \exp_not:n { \kern \c_zero_dim } } } % Handle hpadding and halign \skip_set:Nn \l_tmpa_skip { \dim_max:nn {0pt} { (\@@_get_property:n {min-width}) - \box_wd:N \l_@@_cell_box } } \skip_add:Nn \l_tmpa_skip { 1sp plus 1fil } \str_case_e:nnF {\@@_get_property:n {text-align}} { { right } { \skip_add:Nn \l_@@_left_skip { \l_tmpa_skip } } { center } { \skip_add:Nn \l_@@_left_skip { \l_tmpa_skip / 2 } \skip_add:Nn \l_@@_right_skip { \l_tmpa_skip / 2 } } }{% any other treated as |left| \skip_add:Nn \l_@@_right_skip { \l_tmpa_skip } } \kern\c_zero_dim \tl_if_empty:NF \l_@@_bgcolor_tl { \group_begin: % Paint a background with leaders \tl_use:N \l_@@_bgcolor_tl % install the color \skip_set:Nn \l_tmpa_skip { \l_@@_left_skip + \box_wd:N \l_@@_cell_box + \l_@@_right_skip } \leaders \vrule \skip_horizontal:N \l_tmpa_skip \skip_horizontal:n {-\l_tmpa_skip} \group_end: } \skip_horizontal:N \l_@@_left_skip \box_use_drop:N \l_@@_cell_box \skip_horizontal:N \l_@@_right_skip \kern\c_zero_dim \group_end: } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation}