texlive[59183] Master: tabularray (13may21)

commits+karl at tug.org commits+karl at tug.org
Thu May 13 23:14:08 CEST 2021


Revision: 59183
          http://tug.org/svn/texlive?view=revision&revision=59183
Author:   karl
Date:     2021-05-13 23:14:08 +0200 (Thu, 13 May 2021)
Log Message:
-----------
tabularray (13may21)

Modified Paths:
--------------
    trunk/Master/tlpkg/bin/tlpkg-ctan-check
    trunk/Master/tlpkg/tlpsrc/collection-latexextra.tlpsrc

Added Paths:
-----------
    trunk/Master/texmf-dist/doc/latex/tabularray/
    trunk/Master/texmf-dist/doc/latex/tabularray/README
    trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.pdf
    trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.tex
    trunk/Master/texmf-dist/tex/latex/tabularray/
    trunk/Master/texmf-dist/tex/latex/tabularray/tabularray.sty
    trunk/Master/tlpkg/tlpsrc/tabularray.tlpsrc

Added: trunk/Master/texmf-dist/doc/latex/tabularray/README
===================================================================
--- trunk/Master/texmf-dist/doc/latex/tabularray/README	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/latex/tabularray/README	2021-05-13 21:14:08 UTC (rev 59183)
@@ -0,0 +1,4 @@
+Package: Typeset tabulars and arrays with LaTeX3
+Author: Jianrui Lyu <tolvjr at 163.com>
+Repository: https://github.com/lvjr/tabularray
+License: The LaTeX Project Public License 1.3


Property changes on: trunk/Master/texmf-dist/doc/latex/tabularray/README
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.pdf
===================================================================
(Binary files differ)

Index: trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.pdf
===================================================================
--- trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.pdf	2021-05-13 21:13:13 UTC (rev 59182)
+++ trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.pdf	2021-05-13 21:14:08 UTC (rev 59183)

Property changes on: trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.pdf
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/pdf
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.tex	2021-05-13 21:14:08 UTC (rev 59183)
@@ -0,0 +1,751 @@
+%  -*- coding: utf-8 -*-
+\documentclass[oneside]{book}
+\usepackage[a4paper,margin=2.5cm]{geometry}
+
+\usepackage{codehigh} % https://github.com/lvjr/codehigh
+%\usepackage{arev}
+\usepackage{tabularray}
+\usepackage{array,multirow,amsmath}
+
+\usepackage{hyperref}
+\hypersetup{
+  colorlinks=true,
+  urlcolor=blue3,
+  linkcolor=red3,
+}
+\renewcommand*{\thefootnote}{*}
+
+\newcommand*{\myversion}{2021H}
+\newcommand*{\mydate}{Version \myversion\ (\the\year-\mylpad\month-\mylpad\day)\\\myrepo}
+\newcommand*{\myrepo}{\url{https://github.com/lvjr/tabularray}}
+\newcommand*{\mylpad}[1]{\ifnum#1<10 0\the#1\else\the#1\fi}
+
+\colorlet{highback}{\ifcase\month\or
+  red9\or brown9\or yellow9\or cyan9\or azure9\or blue9\or
+  red9\or brown9\or yellow9\or cyan9\or azure9\or blue9\fi
+}
+\CodeHigh{language=latex/table,style/main=highback,style/code=highback}
+\NewCodeHighEnv{code}{style/main=gray9,style/code=gray9}
+\NewCodeHighEnv{demo}{style/main=gray9,style/code=gray9,demo}
+
+%\CodeHigh{lite}
+
+\begin{document}
+
+\title{\sffamily\color{red3}Tabularray: Typeset Tabulars and Arrays with \LaTeX3}
+\author{Jianrui Lyu (tolvjr at 163.com)}
+\date{\mydate}
+\maketitle
+
+\tableofcontents
+
+\chapter{From Old to New}
+
+\section{Vertical Space}
+
+After loading \verb!tabularrray! package in the preamble,
+we can use \verb!tblr! environments to typeset tabulars and arrays.
+The name \verb!tblr! is short for \verb!tabularray! or \verb!top-bottom-left-right!.
+The following is our first example:
+
+\begin{demo}
+\begin{tabular}{lccr}
+\hline
+ Alpha   & Beta  & Gamma  & Delta \\
+\hline
+ Epsilon & Zeta  & Eta    & Theta \\
+\hline
+ Iota    & Kappa & Lambda & Mu    \\
+\hline
+\end{tabular}
+\end{demo}
+
+\begin{demohigh}
+\begin{tblr}{lccr}
+\hline
+ Alpha   & Beta  & Gamma  & Delta \\
+\hline
+ Epsilon & Zeta  & Eta    & Theta \\
+\hline
+ Iota    & Kappa & Lambda & Mu    \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+You may notice that there is extra space above and below the table rows with \verb!tblr! envirenment.
+This space makes the table look better.
+If you don't like it, you could use \verb!\SetTblrDefault! command:
+
+\begin{demohigh}
+\SetTblrDefault{rowsep=0pt}
+\begin{tblr}{lccr}
+\hline
+ Alpha   & Beta  & Gamma  & Delta \\
+\hline
+ Epsilon & Zeta  & Eta    & Theta \\
+\hline
+ Iota    & Kappa & Lambda & Mu    \\
+\hline
+\end{tblr}
+\end{demohigh} 
+
+But in many cases, this rowsep is useful:
+
+\begin{demo}
+$\begin{array}{rrr}
+\hline
+ \dfrac{2}{3} &  \dfrac{2}{3} &  \dfrac{1}{3} \\
+ \dfrac{2}{3} & -\dfrac{1}{3} & -\dfrac{2}{3} \\
+ \dfrac{1}{3} & -\dfrac{2}{3} &  \dfrac{2}{3} \\
+\hline
+\end{array}$
+\end{demo}
+
+\begin{demohigh}
+$\begin{tblr}{rrr}
+\hline
+ \dfrac{2}{3} &  \dfrac{2}{3} &  \dfrac{1}{3} \\
+ \dfrac{2}{3} & -\dfrac{1}{3} & -\dfrac{2}{3} \\
+ \dfrac{1}{3} & -\dfrac{2}{3} &  \dfrac{2}{3} \\
+\hline
+\end{tblr}$
+\end{demohigh}
+
+Note that you can use \verb!tblr! in both text and math modes.
+
+\section{Multiline Cells}
+
+It's quite easy to write multiline cells without fixing the column width in \verb!tblr! environments:
+just enclose the cell text with braces and use \verb!\\! to break lines:
+
+\begin{demohigh}
+\begin{tblr}{|l|c|r|}
+\hline
+ Left & {Center \\ Cent \\ C} & {Right \\ R} \\
+\hline
+ {L \\ Left} & {C \\ Cent \\ Center} & R \\
+\hline
+\end{tblr}
+\end{demohigh} 
+
+\section{Cell Alignment}
+
+From time to time,
+you may want to specify the horizontal and vertical alignment of cells at the same time.
+\verb!Tabularray! package provides a \verb!Q! column for this
+(In fact, \verb!Q! column is the only primitive column,
+other columns are defined as \verb!Q! columns with some options):
+
+\begin{demohigh}
+\begin{tblr}{|Q[l,t]|Q[c,m]|Q[r,b]|}
+\hline
+ {Top Baseline \\ Left Left} & Middle Center & {Right Right \\ Bottom Baseline} \\
+\hline
+\end{tblr}
+\end{demohigh} 
+
+Note that you can use more meaningful \verb!t! instead of \verb!p! for top baseline alignment.
+For some users who are familiar with word processors,
+these \verb!t! and \verb!b! columns are counter-intuitive.
+In \verb!tabularray! package, there are another two column types \verb!h! and \verb!f!,
+which will align cell text at row head and row foot, respectively:
+
+\begin{demohigh}
+\begin{tblr}{Q[h,4em]Q[t,4em]Q[m,4em]Q[b,4em]Q[f,4em]}
+\hline
+ {row\\head} & {top\\line} & {middle} & {line\\bottom} & {row\\foot} \\
+\hline
+ {row\\head} & {top\\line} & {11\\22\\mid\\44\\55} & {line\\bottom} & {row\\foot} \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+\section{Multirow Cells}
+
+The above \verb!h! and \verb!f! columns are necessary for multirow cells.
+In \verb!tabularray!, the \verb!t! and \verb!b! in the optional argument of \verb!\multirow! commands
+will be treated as \verb!h! and \verb!f!, respectively:
+
+\begin{demo}
+\begin{tabular}{|l|l|l|l|}
+\hline
+ \multirow[t]{4}{1.5cm}{Multirow Cell One} & Alpha &
+ \multirow[b]{4}{1.5cm}{Multirow Cell Two} & Alpha \\
+ & Beta  & & Beta \\
+ & Gamma & & Gamma \\
+ & Delta & & Delta \\
+\hline
+\end{tabular}
+\end{demo}
+
+\begin{demohigh}
+\begin{tblr}{|l|l|l|l|}
+\hline
+ \multirow[t]{4}{1.5cm}{Multirow Cell One} & Alpha &
+ \multirow[b]{4}{1.5cm}{Multirow Cell Two} & Alpha \\
+ & Beta  & & Beta \\
+ & Gamma & & Gamma \\
+ & Delta & & Delta \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+Note that you don't need to load \verb!multirow! package first,
+since \verb!tabularrray! doesn't depend on it.
+Furthermore, \verb!tabularray! will always typeset descent multirow cells.
+First, it will set correct vertical \verb!c! alignment,
+even though some rows have large height:
+
+\begin{demo}
+\begin{tabular}{|l|m{4em}|}
+\hline
+ \multirow[c]{4}{1.5cm}{Multirow} & Alpha  \\
+ & Beta  \\
+ & Gamma \\
+ & Delta Delta Delta \\
+\hline
+\end{tabular}
+\end{demo}
+
+\begin{demohigh}
+\begin{tblr}{|l|m{4em}|}
+\hline
+ \multirow[c]{4}{1.5cm}{Multirow} & Alpha  \\
+ & Beta  \\
+ & Gamma \\
+ & Delta Delta Delta \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+Second, it will enlarge row heights if the multirow cells have large height,
+therefore it always avoids vertical overflow:
+
+\begin{demo}
+\begin{tabular}{|l|m{4em}|}
+\hline
+ \multirow[c]{2}{1cm}{Line \\ Line \\ Line \\ Line} & Alpha \\
+\cline{2-2}
+ & Beta \\
+\hline
+\end{tabular}
+\end{demo}
+
+\begin{demohigh}
+\begin{tblr}{|l|m{4em}|}
+\hline
+ \multirow[c]{2}{1cm}{Line \\ Line \\ Line \\ Line} & Alpha \\
+\cline{2}
+ & Beta \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+\section{Multi Rows and Columns}
+
+It was a hard job to typeset cells with multiple rows and multiple columns. For example:
+
+\begin{demo}
+\begin{tabular}{|c|c|c|c|c|}
+\hline
+ \multirow{2}{*}{2 Rows}
+     & \multicolumn{2}{c|}{2 Columns}
+                 & \multicolumn{2}{c|}{\multirow{2}{*}{2 Rows 2 Columns}} \\
+\cline{2-3}
+     & 2-2 & 2-3 & \multicolumn{2}{c|}{} \\
+\hline
+ 3-1 & 3-2 & 3-3 & 3-4 & 3-5 \\
+\hline
+\end{tabular}
+\end{demo}
+
+With \verb!tabularray! package, you can set spanned cells with \verb!\SetCell! command:
+within the optional argument of \verb!\SetCell! command,
+option \verb!r! is for rowspan number, and \verb!c! for colspan number;
+within the mandatory argument of it, horizontal and vertical alignment options are accepted.
+Therefore it's much simpler to typeset spanned cells:
+
+\begin{demohigh}
+\begin{tblr}{|c|c|c|c|c|}
+\hline
+ \SetCell[r=2]{c} 2 Rows
+     & \SetCell[c=2]{c} 2 Columns
+           &     & \SetCell[r=2,c=2]{c} 2 Rows 2 Columns & \\
+\hline
+     & 2-2 & 2-3 &     &     \\
+\hline
+ 3-1 & 3-2 & 3-3 & 3-4 & 3-5 \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+Using \verb!\multicolumn! command, the omitted cells \textcolor{red3}{must} be removed.
+On the contrary,
+using \verb!\multirow! command, the omitted cells \textcolor{red3}{must not} be removed.
+\verb!\SetCell! command behaves the same as \verb!\multirow! command in this aspect.
+
+With \verb!tblr! environment, any \verb!\hline! segments inside a spanned cell will be ignored,
+therefore we're free to use \verb!\hline! in the above example.
+Also, any omitted cell will definitely be ignored when typesetting,
+no matter it's empty or not.
+With this feature, we could put row and column numbers into the omitted cells,
+which will help us to locate cells when the tables are rather complex:
+
+\begin{demohigh}
+\begin{tblr}{|ll|c|rr|}
+\hline
+ \SetCell[r=3,c=2]{h} r=3 c=2 & 1-2 & \SetCell[r=2,c=3]{r} r=2 c=3 & 1-4 & 1-5 \\ 
+ 2-1 & 2-2 & 2-3 & 2-4 & 2-5 \\
+\hline
+ 3-1 & 3-2 & MIDDLE & \SetCell[r=3,c=2]{f} r=3 c=2 & 3-5 \\
+\hline
+ \SetCell[r=2,c=3]{l} r=2 c=3 & 4-2 & 4-3 & 4-4 & 4-5 \\
+ 5-1 & 5-2 & 5-3 & 5-4 & 5-5 \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+\section{Column Types}
+
+\verb!Tabularray! package supports all normal column types, as well as
+the extendable \verb!X! column type,
+which first occured in \verb!tabularx! package and was largely improved by \verb!tabu! package:
+
+\begin{demohigh}
+\begin{tblr}{|X[2,l]|X[3,l]|X[1,r]|X[r]|}
+\hline
+ Alpha & Beta & Gamma & Delta \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+Also, \verb!X! columns with negative coefficients are possible:
+
+\begin{demohigh}
+\begin{tblr}{|X[2,l]|X[3,l]|X[-1,r]|X[r]|}
+\hline
+ Alpha & Beta & Gamma & Delta \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+We need the width to typeset a table with \verb!X! columns.
+If unset, the default is \verb!\linewidth!.
+To change the width, we have to first put all column specifications into \verb!colspec={...}!:
+
+\begin{demohigh}
+\begin{tblr}{width=0.8\linewidth,colspec={|X[2,l]|X[3,l]|X[-1,r]|X[r]|}}
+\hline
+ Alpha & Beta & Gamma & Delta \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+You can define new column types with \verb!\NewColumnType! command.
+For example, in \verb!tabularray! package,
+\verb!b! and \verb!X! columns are defined as special \verb!Q! columns:
+\begin{codehigh}
+\NewColumnType{b}[1]{Q[b,wd=#1]}
+\NewColumnType{X}[1][]{Q[co=1,#1]}
+\end{codehigh}
+
+\section{Row Types}
+
+Now that we have column types and \verb!colspec! option,
+you may ask for row types and \verb!rowspec! option.
+Yes, they are here:
+
+\begin{demohigh}
+\begin{tblr}{colspec={Q[l]Q[c]Q[r]},rowspec={|Q[t]|Q[m]|Q[b]|}}
+ {Alpha \\ Alpha} & Beta               & Gamma \\
+ Delta            & Epsilon            & {Zeta \\ Zeta}  \\
+ Eta              & {Theta \\ Theta}   & Iota  \\
+\end{tblr}
+\end{demohigh}
+
+Same as column types, \verb!Q! is the only primitive row type,
+and other row types are defined as \verb!Q! types with different options.
+It's better to specify horizontal alignment in \verb!colspec!,
+and vertical alignment in \verb!rowspec!, respectively.
+
+Inside \verb!rowspec!, \verb!|! is the hline type.
+Therefore we need not to write \verb!\hline! commnad, which makes table code cleaner.
+
+\section{Hlines and Vlines}
+
+Hlines and vlines have been improved too. You can specify the widths and styles of them:
+
+\begin{demohigh}
+\begin{tblr}{|l|[dotted]|[2pt]c|r|[solid]|[dashed]|}
+\hline
+One   &  Two  & Three \\
+\hline\hline[dotted]\hline
+Four  & Five  &   Six \\
+\hline[dashed]\hline[1pt]
+Seven & Eight &  Nine \\
+\hline
+\end{tblr}
+\end{demohigh}
+
+\section{Colorful Tables}
+
+To add colors to your tables, you need to load \verb!xcolor! package first.
+\verb!Tabularray! package will also load \verb!ninecolors! package for proper color contrast.
+First you can specify background option for \verb!Q! rows/columns inside \verb!rowspec!/\verb!colspec!:
+
+\begin{demohigh}
+\begin{tblr}{colspec={lcr},rowspec={|Q[cyan7]|Q[azure7]|Q[blue7]|}}
+ Alpha   & Beta  & Gamma  \\
+ Epsilon & Zeta  & Eta    \\
+ Iota    & Kappa & Lambda \\
+\end{tblr}
+\end{demohigh}
+
+\begin{demohigh}
+\begin{tblr}{colspec={Q[l,brown7]Q[c,yellow7]Q[r,olive7]},rowspec={|Q|Q|Q|}}
+ Alpha   & Beta  & Gamma  \\
+ Epsilon & Zeta  & Eta    \\
+ Iota    & Kappa & Lambda \\
+\end{tblr}
+\end{demohigh}
+
+Also you can use \verb!\SetRow! or \verb!\SetColumn! command to specify row or column colors:
+
+\begin{demohigh}
+\begin{tblr}{colspec={lcr},rowspec={|Q|Q|Q|}}
+ \SetRow{cyan7}  Alpha   & Beta  & Gamma  \\
+ \SetRow{azure7} Epsilon & Zeta  & Eta    \\
+ \SetRow{blue7}  Iota    & Kappa & Lambda \\
+\end{tblr}
+\end{demohigh}
+
+\begin{demohigh}
+\begin{tblr}{colspec={lcr},rowspec={|Q|Q|Q|}}
+ \SetColumn{brown7}
+ Alpha          & \SetColumn{yellow7}
+                  Beta            & \SetColumn{olive7}
+                                    Gamma  \\
+ Epsilon        & Zeta            & Eta    \\
+ Iota           & Kappa           & Lambda \\
+\end{tblr}
+\end{demohigh}
+
+Hlines and vlines can also have colors:
+
+\begin{demohigh}
+\begin{tblr}{colspec={lcr},rowspec={|[2pt,green7]Q|[teal7]Q|[green7]Q|[3pt,teal7]}}
+ Alpha   & Beta  & Gamma  \\
+ Epsilon & Zeta  & Eta    \\
+ Iota    & Kappa & Lambda \\
+\end{tblr}
+\end{demohigh}
+
+\begin{demohigh}
+\begin{tblr}{colspec={|[2pt,violet5]l|[2pt,magenta5]c|[2pt,purple5]r|[2pt,red5]}}
+ Alpha   & Beta  & Gamma  \\
+ Epsilon & Zeta  & Eta    \\
+ Iota    & Kappa & Lambda \\
+\end{tblr}
+\end{demohigh}
+
+\section{New Table Commands}
+
+All commands which change the specifications of tables \textcolor{red3}{must} be defined with \verb!\NewTableCommand!.
+The following example demonstrates how to define similar rules as in \verb!booktabs! package:
+
+\begin{demohigh}
+\NewTableCommand\toprule{\hline[0.08em]}
+\NewTableCommand\midrule{\hline[0.05em]}
+\NewTableCommand\bottomrule{\hline[0.08em]}
+\begin{tblr}{llll}
+\toprule
+ Alpha   & Beta  & Gamma   & Delta \\
+\midrule
+ Epsilon & Zeta  & Eta     & Theta \\
+ Iota    & Kappa & Lambda  & Mu    \\
+ Nu      & Xi    & Omicron & Pi    \\
+\bottomrule
+\end{tblr}
+\end{demohigh}
+
+\chapter{New Interface}
+
+With \verb!tabularray! package, you can separate style and content totally in tables.
+
+\section{Hlines and Vlines}
+
+Options \verb!hlines! and \verb!vlines! are for setting all hlines and vlines, respectively.
+With empty value, all hlines/vlines will be solid.
+
+\begin{demohigh}
+\begin{tblr}{hlines,vlines}
+ Alpha   & Beta  & Gamma   & Delta   \\
+ Epsilon & Zeta  & Eta     & Theta   \\
+ Iota    & Kappa & Lambda  & Mu      \\
+ Nu      & Xi    & Omicron & Pi      \\
+ Rho     & Sigma & Tau     & Upsilon \\
+ Phi     & Chi   & Psi     & Omega   \\
+\end{tblr}
+\end{demohigh}
+
+With values inside one pair of braces, all hlines/vlines will be styled.
+
+\begin{demohigh}
+\begin{tblr}{
+ hlines = {1pt,solid},
+ vlines = {red3,dashed},
+}
+ Alpha   & Beta  & Gamma   & Delta   \\
+ Epsilon & Zeta  & Eta     & Theta   \\
+ Iota    & Kappa & Lambda  & Mu      \\
+ Nu      & Xi    & Omicron & Pi      \\
+ Rho     & Sigma & Tau     & Upsilon \\
+ Phi     & Chi   & Psi     & Omega   \\
+\end{tblr}
+\end{demohigh}
+
+Another pair of braces before will select segments in all hlines/vlines.
+
+\begin{demohigh}
+\begin{tblr}{
+ vlines = {1,3,5}{dashed},
+ vlines = {2,4,6}{solid},
+}
+ Alpha   & Beta  & Gamma   & Delta   \\
+ Epsilon & Zeta  & Eta     & Theta   \\
+ Iota    & Kappa & Lambda  & Mu      \\
+ Nu      & Xi    & Omicron & Pi      \\
+ Rho     & Sigma & Tau     & Upsilon \\
+ Phi     & Chi   & Psi     & Omega   \\
+\end{tblr}
+\end{demohigh}
+
+The above example can be simplified with \verb!odd! and \verb!even! values.
+(More child selectors can be defined with \verb!\NewChildSelector! command.
+Advanced users could read the source code for this.)
+
+\begin{demohigh}
+\begin{tblr}{
+ vlines = {odd}{dashed},
+ vlines = {even}{solid},
+}
+ Alpha   & Beta  & Gamma   & Delta   \\
+ Epsilon & Zeta  & Eta     & Theta   \\
+ Iota    & Kappa & Lambda  & Mu      \\
+ Nu      & Xi    & Omicron & Pi      \\
+ Rho     & Sigma & Tau     & Upsilon \\
+ Phi     & Chi   & Psi     & Omega   \\
+\end{tblr}
+\end{demohigh}
+
+Another pair of braces before will draw more hlines/vlines (in which \verb!-! stands for all line segments).
+
+\begin{demohigh}
+\begin{tblr}{
+ hlines = {1}{-}{dashed},
+ hlines = {2}{-}{solid},
+}
+ Alpha   & Beta  & Gamma   & Delta   \\
+ Epsilon & Zeta  & Eta     & Theta   \\
+ Iota    & Kappa & Lambda  & Mu      \\
+ Nu      & Xi    & Omicron & Pi      \\
+ Rho     & Sigma & Tau     & Upsilon \\
+ Phi     & Chi   & Psi     & Omega   \\
+\end{tblr}
+\end{demohigh}
+
+Options \verb!hline{i}! and \verb!vline{j}! are for setting some hlines and vlines, respectively.
+
+\begin{demohigh}
+\begin{tblr}{
+ hline{1,7} = {1pt,solid},
+ hline{3-5} = {blue3,dashed},
+ vline{1,5} = {3-4}{dotted},
+}
+ Alpha   & Beta  & Gamma   & Delta   \\
+ Epsilon & Zeta  & Eta     & Theta   \\
+ Iota    & Kappa & Lambda  & Mu      \\
+ Nu      & Xi    & Omicron & Pi      \\
+ Rho     & Sigma & Tau     & Upsilon \\
+ Phi     & Chi   & Psi     & Omega   \\
+\end{tblr}
+\end{demohigh}
+
+\section{Cells and Spancells}
+
+Option \verb!cells! is for setting all cells.
+
+\begin{demohigh}
+\begin{tblr}{hlines={white},cells={c,blue7}}
+ Alpha   & Beta  & Gamma   & Delta   \\
+ Epsilon & Zeta  & Eta     & Theta   \\
+ Iota    & Kappa & Lambda  & Mu      \\
+ Nu      & Xi    & Omicron & Pi      \\
+ Rho     & Sigma & Tau     & Upsilon \\
+ Phi     & Chi   & Psi     & Omega   \\
+\end{tblr}
+\end{demohigh}
+
+Option \verb!cell{i}{j}! is for setting some cells.
+
+\begin{demohigh}
+\begin{tblr}{
+ hlines = {white},
+ vlines = {white},
+ cell{1,6}{odd} = {teal7},
+ cell{1,6}{even} = {green7},
+ cell{2,4}{1,4} = {red7},
+ cell{3,5}{1,4} = {purple7},
+ cell{2}{2} = {r=4,c=2}{c,azure7},
+}
+ Alpha   & Beta  & Gamma   & Delta   \\
+ Epsilon & Zeta  & Eta     & Theta   \\
+ Iota    & Kappa & Lambda  & Mu      \\
+ Nu      & Xi    & Omicron & Pi      \\
+ Rho     & Sigma & Tau     & Upsilon \\
+ Phi     & Chi   & Psi     & Omega   \\
+\end{tblr}
+\end{demohigh}
+
+\section{Rows and Columns}
+
+Options \verb!rows! and \verb!columns! are for setting all rows and columns, respectively.
+
+\begin{demohigh}
+\begin{tblr}{
+ hlines,
+ vlines,
+ rows = {7mm},
+ columns = {15mm,c},
+}
+ Alpha   & Beta  & Gamma   & Delta \\
+ Epsilon & Zeta  & Eta     & Theta \\
+ Iota    & Kappa & Lambda  & Mu    \\
+\end{tblr}
+\end{demohigh}
+
+Options \verb!row{i}! and \verb!column{j}! are for setting some rows and columns, respectively.
+
+\begin{demohigh}
+\begin{tblr}{
+ hlines = {1pt,white},
+ row{odd} = {blue7},
+ row{even} = {azure7},
+ column{1} = {purple7,c},
+}
+ Alpha   & Beta  & Gamma   & Delta   \\
+ Epsilon & Zeta  & Eta     & Theta   \\
+ Iota    & Kappa & Lambda  & Mu      \\
+ Nu      & Xi    & Omicron & Pi      \\
+ Rho     & Sigma & Tau     & Upsilon \\
+ Phi     & Chi   & Psi     & Omega   \\
+\end{tblr}
+\end{demohigh}
+
+\section{Space in Tables}
+
+Options \verb!rowsep! and \verb!colsep! are for setting padding for rows and columns, respectively.
+
+\begin{demohigh}
+\SetTblrDefault{rowsep=2pt,colsep=2pt}
+\begin{tblr}{hlines,vlines}
+ Alpha   & Beta  & Gamma  & Delta \\
+ Epsilon & Zeta  & Eta    & Theta \\
+ Iota    & Kappa & Lambda & Mu    \\
+\end{tblr}
+\end{demohigh}
+
+Also \verb!abovesep!, \verb!belowsep!, \verb!leftsep!, \verb!rightsep! options are available:
+
+\begin{demohigh}
+\begin{tblr}{
+ hlines,
+ vlines,
+ rows = {abovesep=1pt,belowsep=5pt},
+ columns = {leftsep=1pt,rightsep=5pt},
+}
+ Alpha   & Beta  & Gamma  & Delta \\
+ Epsilon & Zeta  & Eta    & Theta \\
+ Iota    & Kappa & Lambda & Mu    \\
+\end{tblr}
+\end{demohigh}
+
+And \verb!\\[dimen]! can be replaced by \verb!belowsep+! option:
+
+\begin{demohigh}
+\begin{tblr}{
+ hlines, row{2} = {belowsep+=5pt},
+}
+ Alpha   & Beta  & Gamma  & Delta \\
+ Epsilon & Zeta  & Eta    & Theta \\
+ Iota    & Kappa & Lambda & Mu    \\
+\end{tblr}
+\end{demohigh}
+
+Also \verb!\arraystretch! parameter can be replaced by \verb!stretch! option:
+
+\begin{demohigh}
+\begin{tblr}{hlines,stretch=1.5}
+ Alpha   & Beta  & Gamma  & Delta \\
+ Epsilon & Zeta  & Eta    & Theta \\
+ Iota    & Kappa & Lambda & Mu    \\
+\end{tblr}
+\end{demohigh}
+
+\section{Counters in Tables}
+
+Counters \verb!rownum!, \verb!colnum!, \verb!rowcount!, \verb!colcount! can be used in cell text:
+
+\begin{demohigh}
+\begin{tblr}{hlines}
+ Cell[\arabic{rownum}][\arabic{colnum}] & Cell[\arabic{rownum}][\arabic{colnum}] &
+ Cell[\arabic{rownum}][\arabic{colnum}] & Cell[\arabic{rownum}][\arabic{colnum}] \\
+ Cell[\arabic{rownum}][\arabic{colnum}] & Cell[\arabic{rownum}][\arabic{colnum}] &
+ Cell[\arabic{rownum}][\arabic{colnum}] & Cell[\arabic{rownum}][\arabic{colnum}] \\
+ Row=\arabic{rowcount}, Col=\arabic{colcount} &
+ Row=\arabic{rowcount}, Col=\arabic{colcount} &
+ Row=\arabic{rowcount}, Col=\arabic{colcount} &
+ Row=\arabic{rowcount}, Col=\arabic{colcount} \\
+ Cell[\arabic{rownum}][\arabic{colnum}] & Cell[\arabic{rownum}][\arabic{colnum}] &
+ Cell[\arabic{rownum}][\arabic{colnum}] & Cell[\arabic{rownum}][\arabic{colnum}] \\
+ Cell[\arabic{rownum}][\arabic{colnum}] & Cell[\arabic{rownum}][\arabic{colnum}] &
+ Cell[\arabic{rownum}][\arabic{colnum}] & Cell[\arabic{rownum}][\arabic{colnum}] \\
+\end{tblr}
+\end{demohigh}
+
+\section{Experimental Interface}
+
+Everything described in this section is in \underline{\textcolor{red3}{\textbf{experimental}}} status.
+Don’t use it in important documents, unless you have time
+to update them for the newer versions of \verb!tabularray! package in the future.
+
+By default \verb!tabularray! package will compute column widths from span widths.
+By setting option \verb!hspan=minimal!, it will compute span widths from column widths.
+The following two examples show this difference:
+
+\begin{demohigh}
+\SetTblrDefault{hlines,vlines}
+\begin{tblr}{cell{2}{1}={c=2}{l},cell{3}{1}={c=3}{l},cell{4}{2}={c=2}{l}}
+ 111 111 & 222 222 & 333 333 \\
+ 12 Multi Columns Multi Columns 12 & & 333 \\
+ 13 Multi Columns Multi Columns Multi Columns 13 & & \\
+ 111 & 23 Multi Columns Multi Columns 23 & \\
+\end{tblr}
+\end{demohigh}
+
+\begin{demohigh}
+\SetTblrDefault{hlines,vlines,hspan=minimal}
+\begin{tblr}{cell{2}{1}={c=2}{l},cell{3}{1}={c=3}{l},cell{4}{2}={c=2}{l}}
+ 111 111 & 222 222 & 333 333 \\
+ 12 Multi Columns Multi Columns 12 & & 333 \\
+ 13 Multi Columns Multi Columns Multi Columns 13 & & \\
+ 111 & 23 Multi Columns Multi Columns 23 & \\
+\end{tblr}
+\end{demohigh}
+
+\chapter{Source Code}
+
+%\CodeHigh{lite}
+\dochighinput[language=latex/latex3]{tabularray.sty}
+
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/latex/tabularray/tabularray.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/tex/latex/tabularray/tabularray.sty
===================================================================
--- trunk/Master/texmf-dist/tex/latex/tabularray/tabularray.sty	                        (rev 0)
+++ trunk/Master/texmf-dist/tex/latex/tabularray/tabularray.sty	2021-05-13 21:14:08 UTC (rev 59183)
@@ -0,0 +1,3897 @@
+%%% % -*- coding: utf-8 -*-
+%%% ----------------------------------------------------------------------------
+%%% Tabularray: Typeset tabulars and arrays with LaTeX3
+%%% Author    : Jianrui Lyu <tolvjr at 163.com>
+%%% Repository: https://github.com/lvjr/tabularray
+%%% License   : The LaTeX Project Public License 1.3
+%%% ----------------------------------------------------------------------------
+
+%%% --------------------------------------------------------
+%%  \section{Scratch Variables and Data Structures}
+%%% --------------------------------------------------------
+
+\NeedsTeXFormat{LaTeX2e}
+\RequirePackage{expl3}
+\ProvidesExplPackage{tabularray}{2021-05-13}{2021H}
+  {Typeset tabulars and arrays with LaTeX3}
+
+%\RequirePackage{xparse}
+\AtBeginDocument{\@ifpackageloaded{xcolor}{\RequirePackage{ninecolors}}{}}
+
+\ExplSyntaxOn
+
+\cs_if_exist:NF \vbox_set_top_to_ht:Nnn
+  {
+    \cs_new_protected:Npn \vbox_set_top_to_ht:Nnn #1 #2 #3
+      {
+        \tex_setbox:D #1 \tex_vtop:D to \dim_eval:n { #2 }
+          { \color_group_begin: #3 \par \color_group_end: }
+      }
+ }
+
+\cs_generate_variant:Nn \msg_error:nnnn { nnVn }
+\cs_generate_variant:Nn \prop_item:Nn { Ne, NV }
+\cs_generate_variant:Nn \prop_put:Nnn { Nxn, Nxx, NxV }
+\cs_generate_variant:Nn \regex_replace_all:NnN { NVN }
+\cs_generate_variant:Nn \seq_map_indexed_inline:Nn { cn }
+\cs_generate_variant:Nn \tl_gput_right:Nn { Nf }
+\prg_generate_conditional_variant:Nnn \clist_if_in:Nn { Nx } { TF }
+\prg_generate_conditional_variant:Nnn \prop_if_in:Nn { c } { T }
+\prg_generate_conditional_variant:Nnn \str_if_eq:nn { xn } { TF }
+\prg_generate_conditional_variant:Nnn \tl_if_eq:nn { en } { T, TF }
+\prg_generate_conditional_variant:Nnn \tl_if_head_eq_meaning:nN { VN } { T, TF }
+
+\tl_new:N  \l__tblr_a_tl
+\tl_new:N  \l__tblr_b_tl
+\tl_new:N  \l__tblr_c_tl
+\tl_new:N  \l__tblr_d_tl
+\tl_new:N  \l__tblr_e_tl
+\tl_new:N  \l__tblr_f_tl
+\tl_new:N  \l__tblr_h_tl
+\tl_new:N  \l__tblr_i_tl  % for row index
+\tl_new:N  \l__tblr_j_tl  % for column index
+\tl_new:N  \l__tblr_k_tl
+\tl_new:N  \l__tblr_n_tl
+\tl_new:N  \l__tblr_o_tl
+\tl_new:N  \l__tblr_r_tl
+\tl_new:N  \l__tblr_s_tl
+\tl_new:N  \l__tblr_t_tl
+\tl_new:N  \l__tblr_u_tl
+\tl_new:N  \l__tblr_v_tl
+\tl_new:N  \l__tblr_w_tl
+\tl_new:N  \l__tblr_x_tl
+\tl_new:N  \l__tblr_y_tl
+\int_new:N \l__tblr_a_int
+\int_new:N \l__tblr_c_int % for column number
+\int_new:N \l__tblr_r_int % for row number
+\dim_new:N \l__tblr_d_dim % for depth
+\dim_new:N \l__tblr_h_dim % for height
+\dim_new:N \l__tblr_o_dim
+\dim_new:N \l__tblr_p_dim
+\dim_new:N \l__tblr_q_dim
+\dim_new:N \l__tblr_r_dim
+\dim_new:N \l__tblr_s_dim
+\dim_new:N \l__tblr_t_dim
+\dim_new:N \l__tblr_v_dim
+\dim_new:N \l__tblr_w_dim % for width
+\box_new:N \l__tblr_a_box
+\box_new:N \l__tblr_b_box
+\box_new:N \l__tblr_c_box % for cell box
+\box_new:N \l__tblr_d_box
+
+\int_new:N \g_tblr_level_int % store table nesting level
+
+\cs_new_protected:Npn \__tblr_prop_gput:nnn #1 #2 #3
+  {
+    \prop_gput:cnn
+      { g_tblr_#1_ \int_use:N \g_tblr_level_int _prop } { #2 } { #3 }
+  }
+\cs_generate_variant:Nn \__tblr_prop_gput:nnn { nnx, nnV, nxn, nxx, nxV }
+
+\cs_new:Npn \__tblr_prop_item:nn #1 #2
+  {
+    \prop_item:cn { g_tblr_#1_ \int_use:N \g_tblr_level_int _prop } { #2 }
+  }
+\cs_generate_variant:Nn \__tblr_prop_item:nn { ne }
+
+\cs_new_protected:Npn \__tblr_prop_if_in:nnT #1
+  {
+    \prop_if_in:cnT { g_tblr_#1_ \int_use:N \g_tblr_level_int _prop }
+  }
+\cs_new_protected:Npn \__tblr_prop_if_in:nnF #1
+  {
+    \prop_if_in:cnF { g_tblr_#1_ \int_use:N \g_tblr_level_int _prop }
+  }
+\cs_new_protected:Npn \__tblr_prop_if_in:nnTF #1
+  {
+    \prop_if_in:cnTF { g_tblr_#1_ \int_use:N \g_tblr_level_int _prop }
+  }
+\prg_generate_conditional_variant:Nnn \__tblr_prop_if_in:nn { nx } { T, F, TF }
+
+\cs_new_protected:Npn \__tblr_prop_log:n #1
+  {
+    \prop_log:c { g_tblr_#1_ \int_use:N \g_tblr_level_int _prop }
+  }
+
+\cs_new_protected:Npn \__tblr_prop_map_inline:nn #1 #2
+  {
+    \prop_map_inline:cn { g_tblr_#1_ \int_use:N \g_tblr_level_int _prop } {#2}
+  }
+
+\cs_new_protected:Npn \__tblr_prop_gput_if_larger:nnn #1 #2 #3
+  {
+    \__tblr_gput_if_larger:cnn
+      { g_tblr_#1_ \int_use:N \g_tblr_level_int _prop } { #2 } { #3 }
+  }
+\cs_generate_variant:Nn \__tblr_prop_gput_if_larger:nnn { nnx, nnV, nxn, nxx, nxV }
+
+\cs_new_protected:Npn \__tblr_prop_gadd_dimen_value:nnn #1 #2 #3
+  {
+    \__tblr_gadd_dimen_value:cnn
+      { g_tblr_#1_ \int_use:N \g_tblr_level_int _prop } { #2 } { #3 }
+  }
+\cs_generate_variant:Nn \__tblr_prop_gadd_dimen_value:nnn { nnx, nnV, nxn, nxx }
+
+%% Put the dimension to the prop list only if it's larger than the old one
+
+\tl_new:N \l__tblr_put_if_larger_tl
+
+\cs_new_protected:Npn \__tblr_put_if_larger:Nnn #1 #2 #3
+  {
+    \tl_set:Nx \l__tblr_put_if_larger_tl { \prop_item:Nn #1 { #2 } }
+    \bool_lazy_or:nnT
+      { \tl_if_empty_p:N \l__tblr_put_if_larger_tl }
+      { \dim_compare_p:nNn { #3 } > { \l__tblr_put_if_larger_tl } }
+      { \prop_put:Nnn #1 { #2 } { #3 }  }
+  }
+\cs_generate_variant:Nn \__tblr_put_if_larger:Nnn { Nnx, Nxn, Nxx }
+
+\cs_new_protected:Npn \__tblr_gput_if_larger:Nnn #1 #2 #3
+  {
+    \tl_set:Nx \l__tblr_put_if_larger_tl { \prop_item:Nn #1 { #2 } }
+    \bool_lazy_or:nnT
+      { \tl_if_empty_p:N \l__tblr_put_if_larger_tl }
+      { \dim_compare_p:nNn { #3 } > { \l__tblr_put_if_larger_tl } }
+      { \prop_gput:Nnn #1 { #2 } { #3 }  }
+  }
+\cs_generate_variant:Nn \__tblr_gput_if_larger:Nnn { Nnx, Nxn, Nxx, cnn }
+
+%% Add the dimension to some key value of the prop list
+%% #1: the prop list, #2: the key, #3: the dimen to add
+
+\cs_new_protected:Npn \__tblr_add_dimen_value:Nnn #1 #2 #3
+  {
+    \prop_put:Nnx #1 { #2 } { \dim_eval:n { \prop_item:Nn #1 { #2 } + #3 } }
+  }
+\cs_generate_variant:Nn \__tblr_add_dimen_value:Nnn { cnn }
+
+\cs_new_protected:Npn \__tblr_gadd_dimen_value:Nnn #1 #2 #3
+  {
+    \prop_gput:Nnx #1 { #2 } { \dim_eval:n { \prop_item:Nn #1 { #2 } + #3 } }
+  }
+\cs_generate_variant:Nn \__tblr_gadd_dimen_value:Nnn { cnn }
+
+%% Some counters for row and column numbering
+\newcounter{rownum}
+\newcounter{colnum}
+\newcounter{rowcount}
+\newcounter{colcount}
+
+%%% --------------------------------------------------------
+%%  \section{Child Selectors}
+%%% --------------------------------------------------------
+
+\clist_new:N \g_tblr_used_child_selectors_clist
+
+\tl_new:N \l__tblr_childs_arg_spec_tl
+
+\msg_new:nnn { tabularray } { used-child-selector }
+  { Child ~ selector ~ name ~ "#1" ~ has ~ been ~ used! }
+
+\NewDocumentCommand \NewChildSelector { m O{0} o m }
+  {
+    \__tblr_new_child_selector_aux:xnnn { \tl_trim_spaces:n {#1} } {#2} {#3} {#4}
+  }
+
+\cs_new_protected:Npn \__tblr_new_child_selector_aux:nnnn #1 #2 #3 #4
+  {
+    \clist_if_in:NnTF \g_tblr_used_child_selectors_clist { #1 }
+      {
+        \msg_error:nnn { tabularray } { used-child-selector } { #1 }
+        \clist_log:N \g_tblr_used_child_selectors_clist
+      }
+      {
+        \__tblr_make_xparse_arg_spec:nnN { #2 } { #3 } \l__tblr_childs_arg_spec_tl
+        \exp_args:NcV \NewDocumentCommand
+          { __tblr_child_selector_ #1 :w } \l__tblr_childs_arg_spec_tl { #4 }
+        \clist_gput_right:Nn \g_tblr_used_child_selectors_clist { #1 }
+      }
+  }
+\cs_generate_variant:Nn \__tblr_new_child_selector_aux:nnnn { xnnn }
+
+%% #1: argument number, #2: optional argument default, #3: result tl
+\cs_new_protected:Npn \__tblr_make_xparse_arg_spec:nnN #1 #2 #3
+  {
+    \tl_clear:N #3
+    \int_compare:nNnT { #1 } > { 0 }
+      {
+        \IfValueTF { #2 }
+          { \tl_set:Nn #3 { O{#2} } }
+          { \tl_set:Nn #3 { m } }
+        \tl_put_right:Nx #3 { \prg_replicate:nn { #1 - 1 } { m } }
+      }
+  }
+
+\clist_new:N \l_tblr_childs_clist
+\tl_new:N \l_tblr_childs_total_tl
+
+\NewChildSelector { odd }
+  {
+    \int_step_inline:nnnn {1} {2} { \l_tblr_childs_total_tl }
+      { \clist_put_right:Nn \l_tblr_childs_clist {##1} }
+  }
+
+\NewChildSelector { even }
+  {
+    \int_step_inline:nnnn {2} {2} { \l_tblr_childs_total_tl }
+      { \clist_put_right:Nn \l_tblr_childs_clist {##1} }
+  }
+
+\regex_const:Nn \c__tblr_split_selector_name_regex { ^ ( [A-Za-z] {2,} ) ( . * ) }
+\seq_new:N \l__tblr_childs_split_seq
+\seq_new:N \l__tblr_childs_regex_seq
+\tl_new:N \l__tblr_childs_end_tl
+\tl_new:N \l__tblr_childs_selector_tl
+
+%% #1, child specifications; #2, total number.
+%% The result will be put into \l_tblr_childs_clist
+\cs_new_protected:Npn \__tblr_get_childs:nn #1 #2
+  {
+    \clist_clear:N \l_tblr_childs_clist
+    \tl_set:Nx \l_tblr_childs_total_tl {#2}
+    \regex_extract_once:NnNTF \c__tblr_split_selector_name_regex {#1}
+      \l__tblr_childs_regex_seq
+      {
+        \tl_set:No \l__tblr_childs_selector_tl
+          {
+            \cs:w
+            __tblr_child_selector_ \seq_item:Nn \l__tblr_childs_regex_seq {2} :w
+            \cs_end:
+          }
+        \exp_args:Nx \l__tblr_childs_selector_tl
+          { \seq_item:Nn \l__tblr_childs_regex_seq{3} }
+      }
+      {
+        \tl_if_eq:nnTF {#1} {-}
+          { \__tblr_get_childs_normal:nn {1-#2} {#2} }
+          { \__tblr_get_childs_normal:nn {#1} {#2} }
+      }
+    %\clist_log:N \l_tblr_childs_clist
+  }
+\cs_generate_variant:Nn \__tblr_get_childs:nn { nx }
+
+\cs_new_protected:Npn \__tblr_get_childs_normal:nn #1 #2
+  {
+    \seq_set_split:Nnn \l__tblr_childs_split_seq {,} {#1}
+    \seq_map_inline:Nn \l__tblr_childs_split_seq
+      { \__tblr_get_childs_normal_aux:w ##1 - s \scan_stop }
+  }
+
+\cs_new_protected_nopar:Npn \__tblr_get_childs_normal_aux:w #1 - #2 #3 \scan_stop
+  {
+    \tl_if_eq:nnTF {#2} {s}
+      { \tl_set:Nn \l__tblr_childs_end_tl {#1} }
+      { \tl_set:Nn \l__tblr_childs_end_tl {#2} }
+    \int_step_inline:nnn {#1} { \l__tblr_childs_end_tl }
+      { \clist_put_right:Nn \l_tblr_childs_clist {##1} }
+  }
+
+%%% --------------------------------------------------------
+%%  \section{New Table Commands}
+%%% --------------------------------------------------------
+
+%% We need some commands to modify table/row/column/cell specifications.
+%% These commands must be defined with \NewTableCommand command,
+%% so that we could extract them, execute them once, then disable them.
+
+\clist_new:N \g__tblr_table_commands_clist
+
+\msg_new:nnn { tabularray } { defined-table-command }
+  { Table ~ commnad ~ #1 has ~ been ~ defined! }
+
+\NewDocumentCommand \NewTableCommand { m O{0} o m }
+  {
+    \clist_if_in:NnTF \g__tblr_table_commands_clist { #1 }
+      {
+        \msg_error:nnn { tabularray } { defined-table-command } { #1 }
+        \clist_log:N \g__tblr_table_commands_clist
+      }
+      {
+        \__tblr_make_xparse_arg_spec:nnN { #2 } { #3 } \l__tblr_a_tl
+        \exp_args:NcV \NewDocumentCommand
+          { __tblr_table_command_ \cs_to_str:N #1 :w } \l__tblr_a_tl { #4 }
+        \exp_args:NcV \NewDocumentCommand
+          { __tblr_table_command_ \cs_to_str:N #1 _gobble :w } \l__tblr_a_tl { }
+        \IfValueTF { #3 }
+          {
+            \tl_gset:cn { g__tblr_table_cmd_ \cs_to_str:N #1 _arg_numb_tl } {-#2}
+          }
+          {
+            \tl_gset:cn { g__tblr_table_cmd_ \cs_to_str:N #1 _arg_numb_tl } {#2}
+          }
+        \clist_gput_right:Nn \g__tblr_table_commands_clist { #1 }
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_enable_table_commands:
+  {
+    \clist_map_inline:Nn \g__tblr_table_commands_clist
+      { \cs_set_eq:Nc ##1 { __tblr_table_command_ \cs_to_str:N ##1 :w } }
+  }
+
+\cs_new_protected:Npn \__tblr_disable_table_commands:
+  {
+    \clist_map_inline:Nn \g__tblr_table_commands_clist
+      { \cs_set_eq:Nc ##1 { __tblr_table_command_ \cs_to_str:N ##1 _gobble:w } }
+  }
+
+\cs_new_protected:Npn \__tblr_execute_table_commands:
+  {
+    \__tblr_prop_map_inline:nn { command }
+      {
+        \__tblr_set_row_col_from_key_name:w ##1
+        ##2
+      }
+    \LogTblrTracing { cell }
+  }
+
+\cs_new_protected:Npn \__tblr_set_row_col_from_key_name:w [#1][#2]
+  {
+    \int_set:Nn \c at rownum {#1}
+    \int_set:Nn \c at colnum {#2}
+  }
+
+%%% --------------------------------------------------------
+%%  \section{New Dash Styles}
+%%% --------------------------------------------------------
+
+%% \NewDashStyle commands
+
+\dim_zero_new:N \rulewidth
+\dim_set:Nn \rulewidth {0.4pt}
+
+\prop_gset_from_keyval:Nn \g__tblr_defined_hdash_styles_prop
+  { solid = \hrule height \rulewidth }
+\prop_gset_from_keyval:Nn \g__tblr_defined_vdash_styles_prop
+  { solid = \vrule width \rulewidth }
+
+\NewDocumentCommand \NewDashStyle { m m }
+  {
+    \seq_set_split:Nnn \l_tmpa_seq { ~ } {#2}
+    \tl_set:Nx \l__tblr_a_tl { \seq_item:Nn \l_tmpa_seq {1} }
+    \tl_set:Nx \l__tblr_b_tl { \seq_item:Nn \l_tmpa_seq {2} }
+    \tl_set:Nx \l__tblr_c_tl { \seq_item:Nn \l_tmpa_seq {3} }
+    \tl_set:Nx \l__tblr_d_tl { \seq_item:Nn \l_tmpa_seq {4} }
+    \tl_if_eq:NnT \l__tblr_a_tl { on }
+      {
+        \tl_if_eq:NnT \l__tblr_c_tl { off }
+          {
+            \__tblr_dash_style_make_boxes:nxx {#1}
+              { \dim_eval:n {\l__tblr_b_tl} } { \dim_eval:n {\l__tblr_d_tl} }
+          }
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_dash_style_make_boxes:nnn #1 #2 #3
+  {
+    \dim_set:Nn \l_tmpa_dim { #2 + #3 }
+    \tl_set:Nn \l__tblr_h_tl { \hbox_to_wd:nn }
+    \tl_put_right:Nx \l__tblr_h_tl { { \dim_use:N \l_tmpa_dim } }
+    \tl_put_right:Nn \l__tblr_h_tl
+      {
+        { \hss \vbox:n { \hbox_to_wd:nn {#2} {} \hrule height \rulewidth } \hss }
+      }
+    \prop_gput:NnV \g__tblr_defined_hdash_styles_prop {#1} \l__tblr_h_tl
+    %\prop_log:N \g__tblr_defined_hdash_styles_prop
+    \tl_set:Nn \l__tblr_v_tl { \vbox_to_ht:nn }
+    \tl_put_right:Nx \l__tblr_v_tl { { \dim_use:N \l_tmpa_dim } }
+    \tl_put_right:Nn \l__tblr_v_tl
+      {
+        { \vss \hbox:n { \vbox_to_ht:nn {#2} {} \vrule width \rulewidth } \vss }
+      }
+    \prop_gput:NnV \g__tblr_defined_vdash_styles_prop {#1} \l__tblr_v_tl
+    %\prop_log:N \g__tblr_defined_vdash_styles_prop
+  }
+\cs_generate_variant:Nn \__tblr_dash_style_make_boxes:nnn { nxx }
+
+\cs_new_protected:Npn \__tblr_get_hline_dash_style:N #1
+  {
+    \tl_set:Nx \l_tmpa_tl
+      { \prop_item:NV \g__tblr_defined_hdash_styles_prop #1 }
+    \tl_if_empty:NF \l_tmpa_tl { \tl_set_eq:NN #1 \l_tmpa_tl }
+  }
+
+\cs_new_protected:Npn \__tblr_get_vline_dash_style:N #1
+  {
+    \tl_set:Nx \l_tmpa_tl
+      { \prop_item:NV \g__tblr_defined_vdash_styles_prop #1 }
+    \tl_if_empty:NF \l_tmpa_tl { \tl_set_eq:NN #1 \l_tmpa_tl }
+  }
+
+\NewDashStyle {dashed} {on ~ 2pt ~ off ~ 2pt}
+\NewDashStyle {dotted} {on ~ 0.4pt ~ off ~ 1pt}
+
+%%% --------------------------------------------------------
+%%  \section{Set Hlines and Vlines}
+%%% --------------------------------------------------------
+
+\tl_set:Nn \@tblr at dash { dash }
+\tl_set:Nn \@tblr at text { text }
+
+\regex_const:Nn \c__tblr_is_color_key_regex { ^[A-Za-z] }
+
+%% \SetHlines command for setting every hline in the table
+\NewTableCommand \SetHlines [3] [+]
+  {
+    \tblr_set_every_hline:nnn {#1} {#2} {#3}
+  }
+
+%% We put all code inside a group to avoid affecting other table commands
+\cs_new_protected:Npn \tblr_set_every_hline:nnn #1 #2 #3
+  {
+    \group_begin:
+    \int_step_inline:nn { \int_eval:n { \c at rowcount + 1 } }
+      {
+        \int_set:Nn \c at rownum {##1}
+        \tblr_set_hline:nnn {#1} {#2} {#3}
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_every_hline in different ways
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_every_hline_aux:n #1
+  {
+    \tl_if_head_is_group:nTF {#1}
+      {
+        \int_compare:nNnTF { \tl_count:n {#1} } = {3}
+          { \tblr_set_every_hline:nnn #1 }
+          { \tblr_set_every_hline:nnn {1} #1 }
+      }
+      { \tblr_set_every_hline:nnn {1} {-} {#1} }
+  }
+
+%% Add \SetHline, \hline and \cline commands
+
+\tl_new:N \l__tblr_hline_count_tl % the count of all hlines
+\tl_new:N \l__tblr_hline_num_tl   % the index of the hline
+\tl_new:N \l__tblr_hline_cols_tl  % the columns of the hline
+\tl_new:N \l__tblr_hline_dash_tl  % dash style
+\tl_new:N \l__tblr_hline_fg_tl    % dash foreground
+\tl_new:N \l__tblr_hline_wd_tl    % dash width
+
+\NewTableCommand \cline [2] [] { \SetHline [=] {#2} {#1} }
+
+\NewTableCommand \hline [1] [] { \SetHline [+] {-} {#1} }
+
+%% #1: the index of the hline (may be + or =)
+%% #2: which columns of the hline, separate by commas
+%% #3: key=value pairs
+\NewTableCommand \SetHline [3] [+]
+  {
+    \tblr_set_hline:nnn {#1} {#2} {#3}
+  }
+
+%% We need to check "text" key first
+%% If it does exist and has empty value, then do nothing
+\cs_new_protected:Npn \tblr_set_hline:nnn #1 #2 #3
+  {
+    \group_begin:
+    \keys_set_groups:nnn { tblr-hline } { text } {#3}
+    \tl_if_eq:NnF \l__tblr_hline_dash_tl { \exp_not:N \@tblr at text }
+      {
+        \__tblr_set_hline_num:n {#1}
+        \tl_clear:N \l__tblr_hline_dash_tl
+        \keys_set:nn { tblr-hline } { dash = solid, #3 }
+        \__tblr_set_hline_cmd:n {#2}
+      }
+    \group_end:
+  }
+
+\cs_new_protected:Npn \tblr_set_hline:nnnn #1 #2 #3 #4
+  {
+    \group_begin:
+    \__tblr_get_childs:nx {#1} { \int_eval:n { \c at rowcount + 1 } }
+    \clist_map_inline:Nn \l_tblr_childs_clist
+      {
+        \int_set:Nn \c at rownum {##1}
+        \tblr_set_hline:nnn {#2} {#3} {#4}
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_hline in different ways
+%% Note that #1 always includes an outer pair of braces
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_hline_aux:nn #1 #2
+  {
+    \tl_if_head_is_group:nTF {#2}
+      {
+        \int_compare:nNnTF { \tl_count:n {#2} } = {3}
+          { \tblr_set_hline:nnnn #1 #2 }
+          { \tblr_set_hline:nnnn #1 {1} #2 }
+      }
+      { \tblr_set_hline:nnnn #1 {1} {-} {#2} }
+  }
+\cs_generate_variant:Nn \__tblr_set_hline_aux:nn { Vn }
+
+%% #1: the index of hline to set (may be + or =)
+\cs_new_protected:Npn \__tblr_set_hline_num:n #1
+  {
+    \tl_clear:N \l__tblr_hline_num_tl
+    \tl_set:Nx \l__tblr_hline_count_tl
+      { \__tblr_prop_item:ne { hline } { [\int_use:N \c at rownum] / @hline-count } }
+    \tl_if_empty:NTF \l__tblr_hline_count_tl
+      {
+        \tl_set:Nx \l__tblr_hline_num_tl { 1 }
+        \__tblr_prop_gput:nxx { hline }
+          { [\int_use:N \c at rownum] / @hline-count } { 1 }
+      }
+      {
+        \tl_if_eq:nnTF {#1} {+}
+          { \__tblr_set_hline_num_incr: }
+          {
+            \tl_if_eq:nnTF {#1} {=}
+              { \tl_set_eq:NN \l__tblr_hline_num_tl \l__tblr_hline_count_tl }
+              {
+                \int_compare:nNnTF {#1} > { \l__tblr_hline_count_tl }
+                  { \__tblr_set_hline_num_incr: }
+                  { \tl_set:Nn \l__tblr_hline_num_tl {#1} }
+              }
+          }
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_set_hline_num_incr:
+  {
+    \tl_set:Nx \l__tblr_hline_count_tl
+      { \int_eval:n { \l__tblr_hline_count_tl + 1 } }
+    \__tblr_prop_gput:nxx { hline }
+      { [\int_use:N \c at rownum] / @hline-count } { \l__tblr_hline_count_tl }
+    \tl_set_eq:NN \l__tblr_hline_num_tl \l__tblr_hline_count_tl
+  }
+
+\keys_define:nn { tblr-hline }
+  {
+    dash .code:n = \tl_set:Nn \l__tblr_hline_dash_tl { \exp_not:N \@tblr at dash #1 },
+    text .code:n = \tl_set:Nn \l__tblr_hline_dash_tl { \exp_not:N \@tblr at text #1 },
+    text .groups:n = { text },
+    wd .code:n = \tl_set:Nn \l__tblr_hline_wd_tl { \dim_eval:n {#1} },
+    fg .code:n = \tl_set:Nn \l__tblr_hline_fg_tl {#1},
+    baseline .code:n = \__tblr_hline_set_baseline:n {#1},
+    unknown .code:n = \__tblr_hline_unknown_key:V \l_keys_key_str,
+  }
+
+\cs_new_protected:Npn \__tblr_hline_unknown_key:n #1
+  {
+    \prop_if_in:NnTF \g__tblr_defined_hdash_styles_prop {#1}
+      { \tl_set:Nn \l__tblr_hline_dash_tl { \exp_not:N \@tblr at dash #1 } }
+      {
+        \regex_match:NnTF \c__tblr_is_color_key_regex {#1}
+          { \tl_set:Nn \l__tblr_hline_fg_tl {#1} }
+          {
+            \tl_set_rescan:Nnn \l__tblr_v_tl {} {#1}
+            \tl_set:Nn \l__tblr_hline_wd_tl { \dim_eval:n {\l__tblr_v_tl} }
+          }
+      }
+  }
+\cs_generate_variant:Nn \__tblr_hline_unknown_key:n { V }
+
+\cs_new_protected_nopar:Npn \__tblr_set_hline_cmd:n #1
+  {
+    \__tblr_get_childs:nx {#1} { \int_use:N \c at colcount }
+    \clist_map_inline:Nn \l_tblr_childs_clist
+      {
+        \__tblr_prop_gput:nxx { hline }
+          { [\int_use:N \c at rownum][##1](\l__tblr_hline_num_tl) / @dash }
+          { \l__tblr_hline_dash_tl }
+        \tl_if_empty:NF \l__tblr_hline_wd_tl
+          {
+            \__tblr_prop_gput:nxx { hline }
+              { [\int_use:N \c at rownum][##1](\l__tblr_hline_num_tl) / wd }
+              { \l__tblr_hline_wd_tl }
+          }
+        \tl_if_empty:NF \l__tblr_hline_fg_tl
+          {
+            \__tblr_prop_gput:nxx { hline }
+              { [\int_use:N \c at rownum][##1](\l__tblr_hline_num_tl) / fg }
+              { \l__tblr_hline_fg_tl }
+          }
+      }
+  }
+
+\NewTableCommand \firsthline [1] [] { \SetHline [+] {-} { #1, baseline=below } }
+\NewTableCommand \lasthline [1] [] { \SetHline [+] {-} { #1, baseline=above } }
+
+\cs_new_protected:Npn \__tblr_hline_set_baseline:n #1
+  {
+    \tl_if_eq:nnTF {#1} {above}
+      {
+        \__tblr_prop_gput:nnx { table }
+          { baseline } { \int_eval:n { \c at rownum - 1 } }
+      }
+      {
+        \tl_if_eq:nnT {#1} {below}
+          {
+            \__tblr_prop_gput:nnx { table } { baseline } { \int_use:N \c at rownum }
+          }
+      }
+  }
+
+%% \SetVlines command for setting every vline in the table
+\NewTableCommand \SetVlines [3] [+]
+  {
+    \tblr_set_every_vline:nnn {#1} {#2} {#3}
+  }
+
+%% We put all code inside a group to avoid affecting other table commands
+\cs_new_protected:Npn \tblr_set_every_vline:nnn #1 #2 #3
+  {
+    \group_begin:
+    \int_step_inline:nn { \int_eval:n { \c at colcount + 1 } }
+      {
+        \int_set:Nn \c at colnum {##1}
+        \tblr_set_vline:nnn {#1} {#2} {#3}
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_every_vline in different ways
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_every_vline_aux:n #1
+  {
+    \tl_if_head_is_group:nTF {#1}
+      {
+        \int_compare:nNnTF { \tl_count:n {#1} } = {3}
+          { \tblr_set_every_vline:nnn #1 }
+          { \tblr_set_every_vline:nnn {1} #1 }
+      }
+      { \tblr_set_every_vline:nnn {1} {-} {#1} }
+  }
+
+%% Add \SetVline, \vline and \rline commands
+
+\tl_new:N \l__tblr_vline_count_tl % the count of all vlines
+\tl_new:N \l__tblr_vline_num_tl   % the index of the vline
+\tl_new:N \l__tblr_vline_rows_tl  % the rows of the vline
+\tl_new:N \l__tblr_vline_dash_tl  % dash style
+\tl_new:N \l__tblr_vline_fg_tl    % dash foreground
+\tl_new:N \l__tblr_vline_wd_tl    % dash width
+
+\NewTableCommand \rline [2] [] { \SetVline [=] {#2} {#1} }
+
+\NewTableCommand \vline [1] [] { \SetVline [+] {-} {#1} }
+
+%% #1: the index of the vline (may be + or =)
+%% #2: which rows of the vline, separate by commas
+%% #3: key=value pairs
+\NewTableCommand \SetVline [3] [+]
+  {
+    \tblr_set_vline:nnn {#1} {#2} {#3}
+  }
+
+%% We need to check "text" key first
+%% If it does exist and has empty value, then do nothing
+\cs_new_protected:Npn \tblr_set_vline:nnn #1 #2 #3
+  {
+    \group_begin:
+    \keys_set_groups:nnn { tblr-vline } { text } {#3}
+    \tl_if_eq:NnF \l__tblr_vline_dash_tl { \exp_not:N \@tblr at text }
+      {
+        \__tblr_set_vline_num:n {#1}
+        \tl_clear:N \l__tblr_vline_dash_tl
+        \keys_set:nn { tblr-vline } { dash = solid, #3 }
+        \__tblr_set_vline_cmd:n {#2}
+      }
+    \group_end:
+  }
+
+\cs_new_protected:Npn \tblr_set_vline:nnnn #1 #2 #3 #4
+  {
+    \group_begin:
+    \__tblr_get_childs:nx {#1} { \int_eval:n { \c at colcount + 1} }
+    \clist_map_inline:Nn \l_tblr_childs_clist
+      {
+        \int_set:Nn \c at colnum {##1}
+        \tblr_set_vline:nnn {#2} {#3} {#4}
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_vline in different ways
+%% Note that #1 always includes an outer pair of braces
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_vline_aux:nn #1 #2
+  {
+    \tl_if_head_is_group:nTF {#2}
+      {
+        \int_compare:nNnTF { \tl_count:n {#2} } = {3}
+          { \tblr_set_vline:nnnn #1 #2 }
+          { \tblr_set_vline:nnnn #1 {1} #2 }
+      }
+      { \tblr_set_vline:nnnn #1 {1} {-} {#2} }
+  }
+\cs_generate_variant:Nn \__tblr_set_vline_aux:nn { Vn }
+
+%% #1: the index of vline to set (may be + or =)
+\cs_new_protected:Npn \__tblr_set_vline_num:n #1
+  {
+    \tl_clear:N \l__tblr_vline_num_tl
+    \tl_set:Nx \l__tblr_vline_count_tl
+      { \__tblr_prop_item:ne { vline } { [\int_use:N \c at colnum] / @vline-count } }
+    \tl_if_empty:NTF \l__tblr_vline_count_tl
+      {
+        \tl_set:Nx \l__tblr_vline_num_tl { 1 }
+        \__tblr_prop_gput:nxx { vline }
+          { [\int_use:N \c at colnum] / @vline-count } { 1 }
+      }
+      {
+        \tl_if_eq:nnTF {#1} {+}
+          { \__tblr_set_vline_num_incr: }
+          {
+            \tl_if_eq:nnTF {#1} {=}
+              { \tl_set_eq:NN \l__tblr_vline_num_tl \l__tblr_vline_count_tl }
+              {
+                \int_compare:nNnTF {#1} > { \l__tblr_vline_count_tl }
+                  { \__tblr_set_vline_num_incr: }
+                  { \tl_set:Nn \l__tblr_vline_num_tl {#1} }
+              }
+          }
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_set_vline_num_incr:
+  {
+    \tl_set:Nx \l__tblr_vline_count_tl
+      { \int_eval:n { \l__tblr_vline_count_tl + 1 } }
+    \__tblr_prop_gput:nxx { vline }
+      { [\int_use:N \c at colnum] / @vline-count } { \l__tblr_vline_count_tl }
+    \tl_set_eq:NN \l__tblr_vline_num_tl \l__tblr_vline_count_tl
+  }
+
+\keys_define:nn { tblr-vline }
+  {
+    dash .code:n = \tl_set:Nn \l__tblr_vline_dash_tl { \exp_not:N \@tblr at dash #1 },
+    text .code:n = \tl_set:Nn \l__tblr_vline_dash_tl { \exp_not:N \@tblr at text #1 },
+    text .groups:n = { text },
+    wd .code:n = \tl_set:Nn \l__tblr_vline_wd_tl { \dim_eval:n {#1} },
+    fg .code:n = \tl_set:Nn \l__tblr_vline_fg_tl {#1},
+    unknown .code:n = \__tblr_vline_unknown_key:V \l_keys_key_str,
+  }
+
+\cs_new_protected:Npn \__tblr_vline_unknown_key:n #1
+  {
+    \prop_if_in:NnTF \g__tblr_defined_vdash_styles_prop {#1}
+      { \tl_set:Nn \l__tblr_vline_dash_tl { \exp_not:N \@tblr at dash #1 } }
+      {
+        \regex_match:NnTF \c__tblr_is_color_key_regex {#1}
+          { \tl_set:Nn \l__tblr_vline_fg_tl {#1} }
+          {
+            \tl_set_rescan:Nnn \l__tblr_v_tl {} {#1}
+            \tl_set:Nn \l__tblr_vline_wd_tl { \dim_eval:n {\l__tblr_v_tl} }
+          }
+      }
+  }
+\cs_generate_variant:Nn \__tblr_vline_unknown_key:n { V }
+
+\cs_new_protected_nopar:Npn \__tblr_set_vline_cmd:n #1
+  {
+    \__tblr_get_childs:nx {#1} { \int_use:N \c at rowcount }
+    \clist_map_inline:Nn \l_tblr_childs_clist
+      {
+        \__tblr_prop_gput:nxx { vline }
+          { [##1][\int_use:N \c at colnum](\l__tblr_vline_num_tl) / @dash }
+          { \l__tblr_vline_dash_tl }
+        \tl_if_empty:NF \l__tblr_vline_wd_tl
+          {
+            \__tblr_prop_gput:nxx { vline }
+              { [##1][\int_use:N \c at colnum](\l__tblr_vline_num_tl) / wd }
+              { \l__tblr_vline_wd_tl }
+          }
+        \tl_if_empty:NF \l__tblr_vline_fg_tl
+          {
+            \__tblr_prop_gput:nxx { vline }
+              { [##1][\int_use:N \c at colnum](\l__tblr_vline_num_tl) / fg }
+              { \l__tblr_vline_fg_tl }
+          }
+      }
+  }
+
+%%% --------------------------------------------------------
+%%  \section{Set Cells}
+%%% --------------------------------------------------------
+
+%% \SetCells command for setting every cell in the table
+\NewTableCommand \SetCells [2] []
+  {
+    \tblr_set_every_cell:nn {#1} {#2}
+  }
+
+%% We put all code inside a group to avoid affecting other table commands
+\cs_new_protected:Npn \tblr_set_every_cell:nn #1 #2
+  {
+    \group_begin:
+    \int_step_inline:nn { \c at rowcount }
+      {
+        \int_set:Nn \c at rownum {##1}
+        \int_step_inline:nn { \c at colcount }
+          {
+            \int_set:Nn \c at colnum {####1}
+            \tblr_set_cell:nn {#1} {#2}
+          }
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_every_cell in different ways
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_every_cell_aux:n #1
+  {
+    \tl_if_head_is_group:nTF {#1}
+      { \tblr_set_every_cell:nn #1 }
+      { \tblr_set_every_cell:nn {} {#1} }
+  }
+
+%% \SetCell command for multirow and/or multicolumn cells
+
+\NewTableCommand \SetCell [2] []
+  {
+    \tblr_set_cell:nn { #1 } { #2 }
+  }
+
+\tl_new:N \l__tblr_row_span_num_tl
+\tl_new:N \l__tblr_col_span_num_tl
+
+\cs_new_protected:Npn \tblr_set_cell:nn #1 #2
+  {
+    \tl_set:Nn \l__tblr_row_span_num_tl { 1 }
+    \tl_set:Nn \l__tblr_col_span_num_tl { 1 }
+    \keys_set:nn { tblr-cell-span } { #1 }
+    \keys_set:nn { tblr-cell-spec } { #2 }
+    \__tblr_set_span_spec:VV \l__tblr_row_span_num_tl \l__tblr_col_span_num_tl
+  }
+\cs_generate_variant:Nn \tblr_set_cell:nn { nV }
+
+\cs_new_protected:Npn \tblr_set_cell:nnnn #1 #2 #3 #4
+  {
+    \group_begin:
+    \__tblr_get_childs:nx {#1} { \int_use:N \c at rowcount }
+    \clist_set_eq:NN \l_tmpa_clist \l_tblr_childs_clist
+    \__tblr_get_childs:nx {#2} { \int_use:N \c at colcount }
+    \clist_set_eq:NN \l_tmpb_clist \l_tblr_childs_clist
+    \clist_map_inline:Nn \l_tmpa_clist
+      {
+        \int_set:Nn \c at rownum {##1}
+        \clist_map_inline:Nn \l_tmpb_clist
+          {
+            \int_set:Nn \c at colnum {####1}
+            \tblr_set_cell:nn {#3} {#4}
+          }
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_cell in different ways
+%% Note that #1 is always of the type {<i>}{<j>}
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_cell_aux:nn #1 #2
+  {
+    \tl_if_head_is_group:nTF {#2}
+      { \tblr_set_cell:nnnn #1 #2 }
+      { \tblr_set_cell:nnnn #1 {} {#2} }
+  }
+\cs_generate_variant:Nn \__tblr_set_cell_aux:nn { Vn }
+
+\keys_define:nn { tblr-cell-span }
+  {
+    r .tl_set:N = \l__tblr_row_span_num_tl,
+    c .tl_set:N = \l__tblr_col_span_num_tl,
+  }
+
+\keys_define:nn { tblr-cell-spec }
+  {
+    l .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / halign} {l},
+    c .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / halign} {c},
+    r .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / halign} {r},
+    t .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / valign} {t},
+    p .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / valign} {t},
+    m .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / valign} {m},
+    b .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / valign} {b},
+    h .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / valign} {h},
+    f .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / valign} {f},
+    wd .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / width} {#1},
+    bg .code:n = \__tblr_prop_gput:nxx {cell}
+                  {[\int_use:N \c at rownum][\int_use:N \c at colnum] / background} {#1},
+    unknown .code:n = \__tblr_cell_unknown_key:V \l_keys_key_str,
+  }
+
+\cs_new_protected:Npn \__tblr_cell_unknown_key:n #1
+  {
+    \regex_match:NnTF \c__tblr_is_color_key_regex {#1}
+      {
+        \__tblr_prop_gput:nxx {cell}
+          {[\int_use:N \c at rownum][\int_use:N \c at colnum] / background} {#1}
+      }
+      {
+        \tl_set_rescan:Nnn \l__tblr_v_tl {} {#1}
+        \__tblr_prop_gput:nxx {cell}
+          { [\int_use:N \c at rownum][\int_use:N \c at colnum] / width }
+          { \dim_eval:n { \l__tblr_v_tl } }
+      }
+  }
+\cs_generate_variant:Nn \__tblr_cell_unknown_key:n { V }
+
+\cs_new_protected:Npn \__tblr_set_span_spec:nn #1 #2
+  {
+    \int_compare:nNnT { #1 } > { 1 }
+      {
+        \__tblr_prop_gput:nnn {table} {rowspan} {true}
+        \__tblr_prop_gput:nxn {cell}
+          { [\int_use:N \c at rownum][\int_use:N \c at colnum] / rowspan } { #1 }
+      }
+    \int_compare:nNnT { #2 } > { 1 }
+      {
+        \__tblr_prop_gput:nnn {table} {colspan} {true}
+        \__tblr_prop_gput:nxn {cell}
+          { [\int_use:N \c at rownum][\int_use:N \c at colnum] / colspan } { #2 }
+      }
+    \int_step_variable:nnNn
+      { \int_use:N \c at rownum } { \int_eval:n { \c at rownum + #1 - 1 } } \l__tblr_i_tl
+      {
+        \int_step_variable:nnNn
+          { \int_use:N \c at colnum } { \int_eval:n { \c at colnum + #2 - 1 } }
+          \l__tblr_j_tl
+          {
+            \bool_lazy_and:nnF
+              { \int_compare_p:nNn { \l__tblr_i_tl } = { \c at rownum } }
+              { \int_compare_p:nNn { \l__tblr_j_tl } = { \c at colnum } }
+              {
+                \__tblr_prop_gput:nxx {cell}
+                  { [\l__tblr_i_tl][\l__tblr_j_tl] / omit } {true}
+              }
+            \int_compare:nNnF { \l__tblr_i_tl } = { \c at rownum }
+              {
+                \__tblr_prop_gput:nxx {hline}
+                  { [\l__tblr_i_tl][\l__tblr_j_tl] / omit } {true}
+              }
+            \int_compare:nNnF { \l__tblr_j_tl } = { \c at colnum }
+              {
+                \__tblr_prop_gput:nxx {vline}
+                  { [\l__tblr_i_tl][\l__tblr_j_tl] / omit } {true}
+              }
+          }
+      }
+  }
+\cs_generate_variant:Nn \__tblr_set_span_spec:nn { VV }
+
+%% Legacy \multicolumn and \multirow commands
+%% Both of them could be replaced with \SetCell command
+%% Note that they don't have cell text as the last arguments
+
+%% If \multicolumn is followed by \multirow,
+%% We need to call \tblr_set_cell together
+%% in order to omit all hlines inside the span cell.
+\tl_new:N \g__tblr_multicolumn_num_tl
+\tl_new:N \g__tblr_multicolumn_spec_tl
+
+%% There maybe p{2em} inside #2 of \multicolumn command
+\NewTableCommand \multicolumn [2]
+  {
+    \tl_gclear:N \g__tblr_multicolumn_num_tl
+    \tl_gclear:N \g__tblr_multicolumn_spec_tl
+    \tl_map_inline:nn {#2}
+      {
+        \bool_lazy_and:nnF
+          { \tl_if_single_token_p:n {##1} }
+          { \token_if_eq_charcode_p:NN ##1 | }
+          { \tl_put_right:Nn \g__tblr_multicolumn_spec_tl {,##1} }
+      }
+    \peek_meaning:NTF \multirow
+      { \tl_gset:Nn \g__tblr_multicolumn_num_tl {#1} }
+      { \tblr_set_cell:nV { c = #1 } \g__tblr_multicolumn_spec_tl }
+  }
+
+\NewTableCommand \multirow [3] [m]
+  {
+    \tl_if_eq:nnTF {#1} {c}
+      { \tl_set:Nn \l_tmpa_tl {, m} }
+      {
+        \tl_if_eq:nnTF {#1} {t}
+          { \tl_set:Nn \l_tmpa_tl {, h} }
+          { \tl_if_eq:nnTF {#1} {b}
+            { \tl_set:Nn \l_tmpa_tl {, f} }
+            { \tl_set:Nn \l_tmpa_tl {, #1} }
+          }
+      }
+    \tl_if_eq:nnF {#3} {*}
+      { \tl_if_eq:nnF {#3} {=} { \tl_put_right:Nn \l_tmpa_tl {, wd=#3} } }
+    \tl_if_empty:NTF \g__tblr_multicolumn_num_tl
+      { \tblr_set_cell:nV { r = #2 } \l_tmpa_tl }
+      {
+        \tl_put_left:NV \l_tmpa_tl \g__tblr_multicolumn_spec_tl
+        \exp_args:Nx \tblr_set_cell:nV
+          { c = \g__tblr_multicolumn_num_tl, r = #2 } \l_tmpa_tl
+        \tl_gclear:N \g__tblr_multicolumn_num_tl
+      }
+  }
+
+%%% --------------------------------------------------------
+%%  \section{Set Columns and Rows}
+%%% --------------------------------------------------------
+
+%% \SetColumns command for setting every column in the table
+\NewTableCommand \SetColumns [2] []
+  {
+    \tblr_set_every_column:nn {#1} {#2}
+  }
+
+%% We put all code inside a group to avoid affecting other table commands
+\cs_new_protected:Npn \tblr_set_every_column:nn #1 #2
+  {
+    \group_begin:
+    \int_step_inline:nn { \c at colcount }
+      {
+        \int_set:Nn \c at colnum {##1}
+        \tblr_set_column:nn {#1} {#2}
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_every_column in different ways
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_every_column_aux:n #1
+  {
+    \tl_if_head_is_group:nTF {#1}
+      { \tblr_set_every_column:nn #1 }
+      { \tblr_set_every_column:nn {} {#1} }
+  }
+
+%% \SetColumn command for current column or each cells in the column
+
+\NewTableCommand \SetColumn [2] []
+  {
+    \tblr_set_column:nn {#1} {#2}
+  }
+
+\cs_new_protected:Npn \tblr_set_column:nn #1 #2
+  {
+    \keys_set:nn { tblr-column } {#2}
+  }
+
+\cs_new_protected:Npn \tblr_set_column:nnn #1 #2 #3
+  {
+    \group_begin:
+    \__tblr_get_childs:nx {#1} { \int_use:N \c at colcount }
+    \clist_map_inline:Nn \l_tblr_childs_clist
+      {
+        \int_set:Nn \c at colnum {##1}
+        \tblr_set_column:nn {#2} {#3}
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_column in different ways
+%% Note that #1 always includes an outer pair of braces
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_column_aux:nn #1 #2
+  {
+    \tl_if_head_is_group:nTF {#2}
+      { \tblr_set_column:nnn #1 #2 }
+      { \tblr_set_column:nnn #1 {} {#2} }
+  }
+\cs_generate_variant:Nn \__tblr_set_column_aux:nn { Vn }
+
+\keys_define:nn { tblr-column }
+  {
+    l .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { halign } {l},
+    c .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { halign } {c},
+    r .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { halign } {r},
+    t .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { valign } {t},
+    p .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { valign } {t},
+    m .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { valign } {m},
+    b .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { valign } {b},
+    h .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { valign } {h},
+    f .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { valign } {f},
+    bg .code:n = \__tblr_set_key_for_every_column_cell:nnn
+                  { \int_use:N \c at colnum } { background } {#1},
+    wd .code:n = \__tblr_prop_gput:nxx { column }
+                   { [\int_use:N \c at colnum] / width } { \dim_eval:n {#1} },
+    co .code:n = \__tblr_prop_gput:nxx { column }
+                   { [\int_use:N \c at colnum] / coefficient } {#1},
+    leftsep .code:n = \__tblr_prop_gput:nxx { column }
+                   { [\int_use:N \c at colnum] / leftsep } { \dim_eval:n {#1} },
+    rightsep .code:n = \__tblr_prop_gput:nxx { column }
+                   { [\int_use:N \c at colnum] / rightsep } { \dim_eval:n {#1} },
+    colsep .meta:n = { leftsep = #1, rightsep = #1},
+    leftsep+ .code:n = \__tblr_prop_gadd_dimen_value:nxx { column }
+                   { [\int_use:N \c at colnum] / leftsep } { \dim_eval:n {#1} },
+    rightsep+ .code:n = \__tblr_prop_gadd_dimen_value:nxx { column }
+                   { [\int_use:N \c at colnum] / rightsep } { \dim_eval:n {#1} },
+    colsep+ .meta:n = { leftsep+ = #1, rightsep+ = #1},
+    unknown .code:n = \__tblr_column_unknown_key:V \l_keys_key_str,
+  }
+
+%% #1: column number; #2: key; #3: value
+\cs_new_protected:Npn \__tblr_set_key_for_every_column_cell:nnn #1 #2 #3
+  {
+    \int_step_inline:nn { \c at rowcount }
+      {
+        \__tblr_prop_gput:nxn {cell} { [##1][#1] / #2 } {#3}
+      }
+  }
+
+\regex_const:Nn \c__tblr_is_number_key_regex { ^[\+\-]? (\d+|\d*\.\d+)$ }
+
+\cs_new_protected:Npn \__tblr_column_unknown_key:n #1
+  {
+    \regex_match:NnTF \c__tblr_is_number_key_regex {#1}
+      {
+        \__tblr_prop_gput:nxx { column }
+          { [\int_use:N \c at colnum] / coefficient } {#1}
+      }
+      {
+        \regex_match:NnTF \c__tblr_is_color_key_regex {#1}
+          {
+            \__tblr_set_key_for_every_column_cell:nnn
+              { \int_use:N \c at colnum } { background } {#1}
+          }
+          {
+            \tl_set_rescan:Nnn \l__tblr_v_tl {} {#1}
+            \__tblr_prop_gput:nxx { column }
+              { [\int_use:N \c at colnum] / width } { \dim_eval:n { \l__tblr_v_tl } }
+          }
+      }
+  }
+\cs_generate_variant:Nn \__tblr_column_unknown_key:n { V }
+
+%% \SetRows command for setting every row in the table
+\NewTableCommand \SetRows [2] []
+  {
+    \tblr_set_every_row:nn {#1} {#2}
+  }
+
+%% We put all code inside a group to avoid affecting other table commands
+\cs_new_protected:Npn \tblr_set_every_row:nn #1 #2
+  {
+    \group_begin:
+    \int_step_inline:nn { \c at rowcount }
+      {
+        \int_set:Nn \c at rownum {##1}
+        \tblr_set_row:nn {#1} {#2}
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_every_row in different ways
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_every_row_aux:n #1
+  {
+    \tl_if_head_is_group:nTF {#1}
+      { \tblr_set_every_row:nn #1 }
+      { \tblr_set_every_row:nn {} {#1} }
+  }
+
+%% \SetRow command for current row or each cells in the row
+
+\NewTableCommand \SetRow [2] []
+  {
+    \tblr_set_row:nn {#1} {#2}
+  }
+
+\cs_new_protected:Npn \tblr_set_row:nn #1 #2
+  {
+    \keys_set:nn { tblr-row } {#2}
+  }
+
+\cs_new_protected:Npn \tblr_set_row:nnn #1 #2 #3
+  {
+    \group_begin:
+    \__tblr_get_childs:nx {#1} { \int_use:N \c at rowcount }
+    \clist_map_inline:Nn \l_tblr_childs_clist
+      {
+        \int_set:Nn \c at rownum {##1}
+        \tblr_set_row:nn {#2} {#3}
+      }
+    \group_end:
+  }
+
+%% Check the number of arguments and call \tblr_set_row in different ways
+%% Note that #1 always includes an outer pair of braces
+%% This function is called when parsing table specifications
+\cs_new_protected:Npn \__tblr_set_row_aux:nn #1 #2
+  {
+    \tl_if_head_is_group:nTF {#2}
+      { \tblr_set_row:nnn #1 #2 }
+      { \tblr_set_row:nnn #1 {} {#2} }
+  }
+\cs_generate_variant:Nn \__tblr_set_row_aux:nn { Vn }
+
+\keys_define:nn { tblr-row }
+  {
+    l .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { halign } {l},
+    c .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { halign } {c},
+    r .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { halign } {r},
+    t .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { valign } {t},
+    p .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { valign } {t},
+    m .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { valign } {m},
+    b .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { valign } {b},
+    h .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { valign } {h},
+    f .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { valign } {f},
+    bg .code:n = \__tblr_set_key_for_every_row_cell:nnn
+                  { \int_use:N \c at rownum } { background } {#1},
+    ht .code:n = \__tblr_prop_gput:nxx { row }
+                   { [\int_use:N \c at rownum] / height } { \dim_eval:n {#1} },
+    co .code:n = \__tblr_prop_gput:nxx { row }
+                   { [\int_use:N \c at rownum] / coefficient } {#1},
+    abovesep .code:n = \__tblr_prop_gput:nxx { row }
+                   { [\int_use:N \c at rownum] / abovesep } { \dim_eval:n {#1} },
+    belowsep .code:n = \__tblr_prop_gput:nxx { row }
+                   { [\int_use:N \c at rownum] / belowsep } { \dim_eval:n {#1} },
+    rowsep .meta:n = { abovesep = #1, belowsep = #1},
+    abovesep+ .code:n = \__tblr_prop_gadd_dimen_value:nxx { row }
+                   { [\int_use:N \c at rownum] / abovesep } { \dim_eval:n {#1} },
+    belowsep+ .code:n = \__tblr_prop_gadd_dimen_value:nxx { row }
+                   { [\int_use:N \c at rownum] / belowsep } { \dim_eval:n {#1} },
+    rowsep+ .meta:n = { abovesep+ = #1, belowsep+ = #1},
+    nobreak .code:n = \__tblr_prop_gput:nxx { row }
+                   { [\int_eval:n {\c at rownum - 1}] / nobreak } { true },
+    unknown .code:n = \__tblr_row_unknown_key:V \l_keys_key_str,
+  }
+
+%% #1: row number; #2: key; #3: value
+\cs_new_protected:Npn \__tblr_set_key_for_every_row_cell:nnn #1 #2 #3
+  {
+    \int_step_inline:nn { \c at colcount }
+      {
+        \__tblr_prop_gput:nxn {cell} { [#1][##1] / #2 } {#3}
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_row_unknown_key:n #1
+  {
+    \regex_match:NnTF \c__tblr_is_number_key_regex {#1}
+      {
+        \__tblr_prop_gput:nxx { row }
+          { [\int_use:N \c at rownum] / coefficient } {#1}
+      }
+      {
+        \regex_match:NnTF \c__tblr_is_color_key_regex {#1}
+          {
+            \__tblr_set_key_for_every_row_cell:nnn
+              { \int_use:N \c at rownum } { background } {#1}
+          }
+          {
+          \tl_set_rescan:Nnn \l__tblr_v_tl {} {#1}
+            \__tblr_prop_gput:nxx { row }
+              { [\int_use:N \c at rownum] / height } { \dim_eval:n { \l__tblr_v_tl } }
+          }
+      }
+  }
+\cs_generate_variant:Nn \__tblr_row_unknown_key:n { V }
+
+%%% --------------------------------------------------------
+%%  \section{Column Types and Row Types}
+%%% --------------------------------------------------------
+
+%% Some primitive column/row types
+
+\str_const:Nn \c_tblr_primitive_colrow_types_str { Q | < > }
+\tl_new:N \g__tblr_expanded_colrow_spec_tl
+
+\exp_args:Nc \NewDocumentCommand { tblr_primitive_column_type_ Q } { O{} }
+  {
+    \keys_set:nn { tblr-column } { #1 }
+    \int_incr:N \c at colnum
+    \__tblr_execute_colrow_spec_next:N
+  }
+\exp_args:Nc \NewDocumentCommand { tblr_column_type_ Q } { O{} }
+  {
+    \tl_gput_right:Nn \g__tblr_expanded_colrow_spec_tl { Q[#1] }
+    \__tblr_expand_colrow_spec_next:N
+  }
+
+\exp_args:Nc \NewDocumentCommand { tblr_primitive_row_type_ Q } { O{} }
+  {
+    \keys_set:nn { tblr-row } { #1 }
+    \int_incr:N \c at rownum
+    \__tblr_execute_colrow_spec_next:N
+  }
+\exp_args:Nc \NewDocumentCommand { tblr_row_type_ Q } { O{} }
+  {
+    \tl_gput_right:Nn \g__tblr_expanded_colrow_spec_tl { Q[#1] }
+    \__tblr_expand_colrow_spec_next:N
+  }
+
+\exp_args:Nc \NewDocumentCommand { tblr_primitive_column_type_ | } { O{} }
+  {
+    \vline [#1]
+    \__tblr_execute_colrow_spec_next:N
+  }
+\exp_args:Nc \NewDocumentCommand { tblr_column_type_ | } { O{} }
+  {
+    \tl_gput_right:Nn \g__tblr_expanded_colrow_spec_tl { |[#1] }
+    \__tblr_expand_colrow_spec_next:N
+  }
+
+\exp_args:Nc \NewDocumentCommand { tblr_primitive_row_type_ | } { O{} }
+  {
+    \hline [#1]
+    \__tblr_execute_colrow_spec_next:N
+  }
+\exp_args:Nc \NewDocumentCommand { tblr_row_type_ | } { O{} }
+  {
+    \tl_gput_right:Nn \g__tblr_expanded_colrow_spec_tl { |[#1] }
+    \__tblr_expand_colrow_spec_next:N
+  }
+
+\exp_args:Nc \NewDocumentCommand { tblr_primitive_column_type_ > } { O{} m }
+  {
+    \tl_if_blank:nF { #1 }
+      {
+        \__tblr_prop_gput:nxx
+          { column }
+          { [\int_use:N \c at colnum] / leftsep}
+          { \dim_eval:n { #1 } }
+      }
+    \tl_if_blank:nF { #2 }
+      {
+        \int_step_variable:nNn { \c at rowcount } \l__tblr_i_tl
+          {
+            \tl_set:Nx \l_tmpa_tl
+            {
+              \__tblr_prop_item:ne {text}
+                { [\l__tblr_i_tl][\int_use:N \c at colnum] }
+            }
+            \tl_put_left:Nn \l_tmpa_tl { #2 }
+            \__tblr_prop_gput:nxV {text}
+              { [\l__tblr_i_tl][\int_use:N \c at colnum] } \l_tmpa_tl
+          }
+      }
+    \__tblr_execute_colrow_spec_next:N
+  }
+\exp_args:Nc \NewDocumentCommand { tblr_column_type_ > } { O{} m }
+  {
+    \tl_gput_right:Nn \g__tblr_expanded_colrow_spec_tl { >[#1]{#2} }
+    \__tblr_expand_colrow_spec_next:N
+  }
+
+\exp_args:Nc \NewDocumentCommand { tblr_primitive_row_type_ > } { O{} m }
+  {
+    \tl_if_blank:nF { #1 }
+      {
+        \__tblr_prop_gput:nxx
+          { row }
+          { [\int_use:N \c at rownum] / abovesep }
+          { \dim_eval:n { #1 } }
+      }
+    \tl_if_blank:nF { #2 }
+      {
+        \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+          {
+            \tl_set:Nx \l_tmpa_tl
+            {
+              \__tblr_prop_item:ne {text}
+                { [\int_use:N \c at rownum][\l__tblr_j_tl] }
+            }
+            \tl_put_left:Nn \l_tmpa_tl { #2 }
+            \__tblr_prop_gput:nxV {text}
+              { [\int_use:N \c at rownum][\l__tblr_j_tl] } \l_tmpa_tl
+          }
+      }
+    \__tblr_execute_colrow_spec_next:N
+  }
+\exp_args:Nc \NewDocumentCommand { tblr_row_type_ > } { O{} m }
+  {
+    \tl_gput_right:Nn \g__tblr_expanded_colrow_spec_tl { >[#1]{#2} }
+    \__tblr_expand_colrow_spec_next:N
+  }
+
+\exp_args:Nc \NewDocumentCommand { tblr_primitive_column_type_ < } { O{} m }
+  {
+    \tl_if_blank:nF { #1 }
+      {
+        \__tblr_prop_gput:nxx
+          { column }
+          { [\int_eval:n {\c at colnum - 1}] / rightsep }
+          { \dim_eval:n { #1 } }
+      }
+    \tl_if_blank:nF { #2 }
+      {
+        \int_step_variable:nNn { \c at rowcount } \l__tblr_i_tl
+          {
+            \tl_set:Nx \l_tmpa_tl
+            {
+              \__tblr_prop_item:ne {text}
+                { [\l__tblr_i_tl][\int_eval:n {\c at colnum - 1}] }
+            }
+            \tl_put_right:Nn \l_tmpa_tl { #2 }
+            \__tblr_prop_gput:nxV {text}
+              { [\l__tblr_i_tl][\int_eval:n {\c at colnum - 1}] } \l_tmpa_tl
+          }
+      }
+    \__tblr_execute_colrow_spec_next:N
+  }
+\exp_args:Nc \NewDocumentCommand { tblr_column_type_ < } { O{} m }
+  {
+    \tl_gput_right:Nn \g__tblr_expanded_colrow_spec_tl { <[#1]{#2} }
+    \__tblr_expand_colrow_spec_next:N
+  }
+
+\exp_args:Nc \NewDocumentCommand { tblr_primitive_row_type_ < } { O{} m }
+  {
+    \tl_if_blank:nF { #1 }
+      {
+        \__tblr_prop_gput:nxx
+          { row }
+          { [\int_eval:n {\c at rownum - 1}] / belowsep }
+          { \dim_eval:n { #1 } }
+      }
+    \tl_if_blank:nF { #2 }
+      {
+        \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+          {
+            \tl_set:Nx \l_tmpa_tl
+            {
+              \__tblr_prop_item:ne {text}
+                { [\int_eval:n {\c at rownum - 1}][\l__tblr_j_tl] }
+            }
+            \tl_put_right:Nn \l_tmpa_tl { #2 }
+            \__tblr_prop_gput:nxV {text}
+              { [\int_eval:n {\c at rownum - 1}][\l__tblr_j_tl] } \l_tmpa_tl
+          }
+      }
+    \__tblr_execute_colrow_spec_next:N
+  }
+\exp_args:Nc \NewDocumentCommand { tblr_row_type_ < } { O{} m }
+  {
+    \tl_gput_right:Nn \g__tblr_expanded_colrow_spec_tl { <[#1]{#2} }
+    \__tblr_expand_colrow_spec_next:N
+  }
+
+%% \NewColumnType/\NewRowType command and predefined column/row types
+
+\str_new:N \g_tblr_used_column_types_str
+\str_gset_eq:NN \g_tblr_used_column_types_str \c_tblr_primitive_colrow_types_str
+
+\str_new:N \g_tblr_used_row_types_str
+\str_gset_eq:NN \g_tblr_used_row_types_str \c_tblr_primitive_colrow_types_str
+
+\bool_new:N \g__tblr_colrow_spec_expand_stop_bool
+\tl_new:N \g__tblr_column_or_row_tl
+
+\msg_new:nnn { tabularray } { used-colrow-type }
+  { #1 ~ type ~ name ~ #2 ~ has ~ been ~ used! }
+
+\NewDocumentCommand \NewColumnType { m O{0} o m }
+  {
+    \tl_set:Nn \g__tblr_column_or_row_tl { column }
+    \__tblr_new_column_or_row_type:nnnn {#1} {#2} {#3} {#4}
+  }
+
+\NewDocumentCommand \NewRowType { m O{0} o m }
+  {
+    \tl_set:Nn \g__tblr_column_or_row_tl { row }
+    \__tblr_new_column_or_row_type:nnnn {#1} {#2} {#3} {#4}
+  }
+
+\NewDocumentCommand \NewColumnRowType { m O{0} o m }
+  {
+    \tl_set:Nn \g__tblr_column_or_row_tl { column }
+    \__tblr_new_column_or_row_type:nnnn {#1} {#2} {#3} {#4}
+    \tl_set:Nn \g__tblr_column_or_row_tl { row }
+    \__tblr_new_column_or_row_type:nnnn {#1} {#2} {#3} {#4}
+  }
+
+\cs_new_protected:Npn \__tblr_new_column_or_row_type:nnnn #1 #2 #3 #4
+  {
+    \str_if_in:cnTF { g_tblr_used_ \g__tblr_column_or_row_tl _types_str } {#1}
+      {
+        \tl_if_eq:NnTF \g__tblr_column_or_row_tl { row }
+          { \msg_warning:nnnn { tabularray } { used-colrow-type } { Row } {#1} }
+          { \msg_warning:nnnn { tabularray } { used-colrow-type } { Column } {#1} }
+        \str_log:c { g_tblr_used_ \g__tblr_column_or_row_tl _types_str }
+      }
+      {
+        \__tblr_make_xparse_arg_spec:nnN {#2} {#3} \l__tblr_a_tl
+        \exp_args:NcV \NewDocumentCommand
+          { tblr_ \g__tblr_column_or_row_tl _type_ #1 } \l__tblr_a_tl
+          {
+            \bool_gset_false:N \g__tblr_colrow_spec_expand_stop_bool
+            \tl_gput_right:Nf \g__tblr_expanded_colrow_spec_tl {#4}
+            \__tblr_expand_colrow_spec_next:N
+          }
+        \str_gput_right:cn
+          { g_tblr_used_ \g__tblr_column_or_row_tl _types_str } {#1}
+      }
+  }
+
+\NewColumnRowType { l } { Q[l] }
+\NewColumnRowType { c } { Q[c] }
+\NewColumnRowType { r } { Q[r] }
+
+\NewColumnType { t } [1] { Q[t,wd=#1] }
+\NewColumnType { p } [1] { Q[p,wd=#1] }
+\NewColumnType { m } [1] { Q[m,wd=#1] }
+\NewColumnType { b } [1] { Q[b,wd=#1] }
+\NewColumnType { h } [1] { Q[h,wd=#1] }
+\NewColumnType { f } [1] { Q[f,wd=#1] }
+
+\NewRowType { t } [1] { Q[t,ht=#1] }
+\NewRowType { p } [1] { Q[p,ht=#1] }
+\NewRowType { m } [1] { Q[m,ht=#1] }
+\NewRowType { b } [1] { Q[b,ht=#1] }
+\NewRowType { h } [1] { Q[h,ht=#1] }
+\NewRowType { f } [1] { Q[f,ht=#1] }
+
+\NewColumnRowType { X } [1][] { Q[co=1,#1] }
+
+\NewColumnRowType { ! } [1] { |[text={#1}] }
+\NewColumnRowType { @ } [1] { <[0pt]{} |[text={#1}] >[0pt]{} }
+\NewColumnRowType { * } [2] { \prg_replicate:nn {#1} {#2} }
+
+\cs_new_protected:Npn \__tblr_parse_colrow_spec:nn #1 #2
+  {
+    \tl_gset:Nn \g__tblr_column_or_row_tl {#1}
+    \tl_gset:Nn \g__tblr_expanded_colrow_spec_tl {#2}
+    \__tblr_expand_colrow_spec:N \g__tblr_expanded_colrow_spec_tl
+    \__tblr_execute_colrow_spec:N \g__tblr_expanded_colrow_spec_tl
+  }
+
+%% Expand defined column/row types
+
+\cs_new_protected:Npn \__tblr_expand_colrow_spec:N #1
+  {
+    \bool_do_until:Nn \g__tblr_colrow_spec_expand_stop_bool
+      {
+        \LogTblrTracing { colspec, rowspec }
+        \bool_gset_true:N \g__tblr_colrow_spec_expand_stop_bool
+        \tl_set_eq:NN \l_tmpa_tl #1
+        \tl_gclear:N #1
+        \exp_last_unbraced:NV
+          \__tblr_expand_colrow_spec_next:N \l_tmpa_tl \scan_stop:
+      }
+  }
+
+\msg_new:nnn { tabularray } { unexpandable-colrow-type }
+  { Unexpandable ~ command ~ #2 inside ~ #1 ~ type! }
+
+\msg_new:nnn { tabularray } { unknown-colrow-type }
+  { Unknown ~ #1 ~ type ~ #2! }
+
+\cs_new_protected:Npn \__tblr_expand_colrow_spec_next:N #1
+  {
+    \token_if_eq_catcode:NNTF #1 \scan_stop:
+      {
+        \token_if_eq_meaning:NNF #1 \scan_stop:
+          {
+            \msg_error:nnVn { tabularray } { unexpandable-colrow-type }
+              \g__tblr_column_or_row_tl {#1}
+          }
+      }
+      {
+        \str_if_in:cnTF { g_tblr_used_ \g__tblr_column_or_row_tl _types_str } {#1}
+          { \cs:w tblr_ \g__tblr_column_or_row_tl _type_  #1 \cs_end: }
+          {
+            \msg_error:nnVn { tabularray } { unknown-colrow-type }
+              \g__tblr_column_or_row_tl {#1}
+            \str_log:c { g_tblr_used_ \g__tblr_column_or_row_tl _types_str }
+          }
+      }
+  }
+
+%% Execute primitive column/row types
+
+\cs_new_protected:Npn \__tblr_execute_colrow_spec:N #1
+  {
+    \tl_if_eq:NnTF \g__tblr_column_or_row_tl { row }
+      { \int_set:Nn \c at rownum {1} }
+      { \int_set:Nn \c at colnum {1} }
+    \exp_last_unbraced:NV \__tblr_execute_colrow_spec_next:N #1 \scan_stop:
+  }
+
+\cs_new_protected:Npn \__tblr_execute_colrow_spec_next:N #1
+  {
+    \token_if_eq_meaning:NNF #1 \scan_stop:
+      { \cs:w tblr_primitive_ \g__tblr_column_or_row_tl _type_  #1 \cs_end: }
+  }
+
+%%% --------------------------------------------------------
+%%  \section{Tabularray Environments}
+%%% --------------------------------------------------------
+
+\tl_new:N \l__tblr_env_name_tl
+\bool_new:N \l__tblr_math_mode_bool
+
+\NewDocumentEnvironment { tblr } { O{c} m +b }
+  {
+    \tl_set:Nn \l__tblr_env_name_tl { tblr }
+    \mode_if_math:TF
+      { \bool_set_true:N \l__tblr_math_mode_bool }
+      { \bool_set_false:N \l__tblr_math_mode_bool }
+    \buildtblr {#1} {#2} {#3}
+  } { }
+
+%% Read, split and build the table
+
+\cs_new_protected:Npn \buildtblr #1 #2 #3
+  {
+    \mode_leave_vertical:
+    \int_gincr:N \g_tblr_level_int
+    \__tblr_clear_prop_lists:
+    \__tblr_enable_table_commands:
+    \__tblr_split_table:n { #3 }
+    \LogTblrTracing { command }
+    \__tblr_initial_table_spec:
+    \LogTblrTracing { table }
+    \__tblr_parse_table_spec:n { #2 }
+    \__tblr_execute_table_commands:
+    \__tblr_disable_table_commands:
+    \__tblr_calc_cell_and_line_sizes:
+    \__tblr_build_whole:n { #1 }
+    \int_gdecr:N \g_tblr_level_int
+  }
+
+\cs_new_protected:Npn \__tblr_clear_prop_lists:
+  {
+    \prop_gclear_new:c { g_tblr_text_ \int_use:N \g_tblr_level_int _prop }
+    \prop_gclear_new:c { g_tblr_command_ \int_use:N \g_tblr_level_int _prop }
+    \prop_gclear_new:c { g_tblr_table_ \int_use:N \g_tblr_level_int _prop }
+    \prop_gclear_new:c { g_tblr_row_ \int_use:N \g_tblr_level_int _prop }
+    \prop_gclear_new:c { g_tblr_column_ \int_use:N \g_tblr_level_int _prop }
+    \prop_gclear_new:c { g_tblr_cell_ \int_use:N \g_tblr_level_int _prop }
+    \prop_gclear_new:c { g_tblr_hline_ \int_use:N \g_tblr_level_int _prop }
+    \prop_gclear_new:c { g_tblr_vline_ \int_use:N \g_tblr_level_int _prop }
+  }
+
+%% Insert and remove braces for nesting environments inside cells
+%% These make line split and cell split workable
+%% We need to replace N times for N level nestings
+\regex_const:Nn \c__tblr_insert_braces_regex
+  {
+    \c{begin} \cB\{ (\c[^BE].*) \cE\} (.*?) \c{end} \cB\{ (\c[^BE].*) \cE\}
+  }
+\tl_const:Nn \c__tblr_insert_braces_tl
+  {
+    \c{begin} \cB\{ \cB\{ \1 \cE\} \2 \c{end} \cE\} \cB\{ \3 \cE\}
+  }
+\regex_const:Nn \c__tblr_remove_braces_regex
+  {
+    \c{begin} \cB\{ \cB\{ (.*?) \c{end} \cE\}
+  }
+\tl_const:Nn \c__tblr_remove_braces_tl
+  {
+    \c{begin} \cB\{ \1 \c{end}
+  }
+\cs_new_protected:Npn \__tblr_insert_braces:N #1
+  {
+    \regex_replace_all:NVN \c__tblr_insert_braces_regex \c__tblr_insert_braces_tl #1
+    \regex_replace_all:NVN \c__tblr_insert_braces_regex \c__tblr_insert_braces_tl #1
+  }
+\cs_new_protected:Npn \__tblr_remove_braces:N #1
+  {
+    \regex_replace_all:NVN \c__tblr_remove_braces_regex \c__tblr_remove_braces_tl #1
+    \regex_replace_all:NVN \c__tblr_remove_braces_regex \c__tblr_remove_braces_tl #1
+  }
+
+%% Split table content to cells and store them
+%% #1: table content
+
+\seq_new:N \l_tblr_lines_seq
+
+\cs_new_protected:Npn \__tblr_split_table:n #1
+  {
+    \int_zero:N \c at rowcount
+    \int_zero:N \c at colcount
+    \__tblr_split_table_to_lines:nN { #1 } \l_tblr_lines_seq
+    \__tblr_split_lines_to_cells:N \l_tblr_lines_seq
+  }
+
+%% Split table content to a sequence of lines
+%% #1: table content, #2: resulting sequence of lines
+\cs_new_protected:Npn \__tblr_split_table_to_lines:nN #1 #2
+  {
+    \tl_set:Nn \l_tmpa_tl { #1 }
+    \__tblr_insert_braces:N \l_tmpa_tl
+    \seq_set_split:NnV \l_tmpa_seq { \\ } \l_tmpa_tl
+    \seq_clear:N #2
+    \seq_map_inline:Nn \l_tmpa_seq
+      {
+        \tl_if_head_eq_meaning:nNTF {##1} *
+          {
+            \tl_set:Nn \l__tblr_b_tl { \SetRow{nobreak} }
+            \tl_set:Nx \l__tblr_c_tl { \tl_tail:n {##1} }
+            \tl_trim_spaces:N \l__tblr_c_tl %% Ignore spaces between * and [dimen]
+            \tl_log:N \l__tblr_c_tl
+            \tl_if_head_eq_meaning:VNT \l__tblr_c_tl [
+              {
+                \tl_put_right:Nn \l__tblr_b_tl { \RowBefore at AddBelowSep }
+              }
+            \tl_put_right:NV \l__tblr_b_tl \l__tblr_c_tl
+            \seq_put_right:NV #2 \l__tblr_b_tl
+          }
+          {
+            \tl_if_head_eq_meaning:nNTF { ##1 } [
+              { \seq_put_right:Nn #2 { \RowBefore at AddBelowSep ##1 } }
+              { \seq_put_right:Nn #2 { ##1 } }
+          }
+      }
+    \int_set:Nn \c at rowcount { \seq_count:N #2 }
+  }
+
+%% Treat \\[dimen] command
+\NewTableCommand \RowBefore at AddBelowSep [1] []
+  {
+    \IfValueT { #1 }
+      {
+        \__tblr_prop_gadd_dimen_value:nxx { row }
+          { [\int_eval:n {\c at rownum - 1}] / belowsep } { #1 }
+      }
+  }
+
+%% Split table lines to cells and store them
+%% #1: sequence of lines
+\cs_new_protected:Npn \__tblr_split_lines_to_cells:N #1
+  {
+    \seq_map_indexed_function:NN #1 \__tblr_split_one_line:nn
+    \LogTblrTracing { text }
+  }
+
+%% Split one line into cells and store them
+%% #1: row number, #2 the line text
+\cs_new_protected:Npn \__tblr_split_one_line:nn #1 #2
+  {
+    \seq_set_split:Nnn \l_tmpa_seq { & } { #2 }
+    \int_set:Nn \c at rownum {#1}
+    \int_zero:N \c at colnum
+    \seq_map_inline:Nn \l_tmpa_seq
+      {
+        \tl_set:Nn \l_tmpa_tl { ##1 }
+        \__tblr_remove_braces:N \l_tmpa_tl
+        \int_incr:N \c at colnum
+        \__tblr_extract_table_commands:N \l_tmpa_tl
+        \__tblr_prop_gput:nxV {text} { [#1][\int_use:N \c at colnum] } \l_tmpa_tl
+        \__tblr_add_multicolumn_empty_cell:
+      }
+    %% Decrease row count by 1 if the last row has only one empty cell text
+    %% We need to do it here since the > or < column type may add text to cells
+    \bool_lazy_and:nnTF
+      { \int_compare_p:nNn {\c at colnum} = {1} }
+      { \tl_if_empty_p:N \l_tmpa_tl }
+      { \int_decr:N \c at rowcount }
+      {
+        \__tblr_prop_gput:nnx
+          {row} { [#1] / cell-number } { \int_use:N \c at colnum }
+        \int_compare:nT { \c at colnum > \c at colcount }
+          {
+            \int_set_eq:NN \c at colcount \c at colnum
+          }
+      }
+  }
+
+%% Add empty cells after the \multicolumn span cell
+\cs_new_protected:Npn \__tblr_add_multicolumn_empty_cell:
+  {
+    \int_step_inline:nn { \l__multicolumn_cell_number_int - 1 }
+      {
+        \int_incr:N \c at colnum
+        \__tblr_prop_gput:nxn {text}
+          { [\int_use:N \c at rownum][\int_use:N \c at colnum] } { }
+      }
+  }
+
+%%% --------------------------------------------------------
+%%  \section{Extract Table Commands from Cell Text}
+%%% --------------------------------------------------------
+
+%% Extract table commands defined with \NewTableCommand from cell text
+
+\clist_gset:Nn \g__tblr_table_commands_unbrace_next_clist {\multirow, \multicolumn}
+\bool_new:N \l__tblr_table_command_unbrace_next_bool
+\int_new:N \l__multicolumn_cell_number_int
+\tl_new:N \l__tblr_saved_table_commands_before_cell_text_tl
+\tl_new:N \l__tblr_saved_cell_text_after_table_commands_tl
+
+\cs_new_protected:Npn \__tblr_extract_table_commands:N #1
+  {
+    \tl_clear:N \l__tblr_saved_table_commands_before_cell_text_tl
+    \tl_clear:N \l__tblr_saved_cell_text_after_table_commands_tl
+    \int_set:Nn \l__multicolumn_cell_number_int {1}
+    \exp_last_unbraced:NV \__tblr_extract_table_commands_next:w #1 \scan_stop:
+    \tl_if_empty:NF \l__tblr_saved_table_commands_before_cell_text_tl
+      {
+        \__tblr_prop_gput:nxV { command }
+          {[\int_use:N \c at rownum][\int_use:N \c at colnum]}
+          \l__tblr_saved_table_commands_before_cell_text_tl
+      }
+    \tl_set_eq:NN #1 \l__tblr_saved_cell_text_after_table_commands_tl
+  }
+
+%% #1 maybe a single token or multiple tokens given in braces
+\cs_new_protected:Npn \__tblr_extract_table_commands_next:w #1
+  {
+    \clist_if_in:NnTF \g__tblr_table_commands_clist { #1 }
+      {
+        \clist_if_in:NnTF \g__tblr_table_commands_unbrace_next_clist { #1 }
+          { \bool_set_true:N \l__tblr_table_command_unbrace_next_bool }
+          { \bool_set_false:N \l__tblr_table_command_unbrace_next_bool }
+        \token_if_eq_meaning:NNTF #1 \multicolumn
+          { \__tblr_extract_multicolumn_command:Nn #1 }
+          { \__tblr_extract_one_table_command:N #1 }
+      }
+      {
+        \tl_if_single_token:nTF {#1}
+          {
+            \token_if_eq_meaning:NNF #1 \scan_stop:
+              { \__tblr_save_real_cell_text:w #1 }
+          }
+          { \__tblr_save_real_cell_text:w {#1} }
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_extract_multicolumn_command:Nn #1 #2
+  {
+    \int_set:Nn \l__multicolumn_cell_number_int {#2}
+    \__tblr_extract_one_table_command:N #1 {#2}
+  }
+
+\cs_new_protected:Npn \__tblr_extract_one_table_command:N #1
+  {
+    \int_set:Nn \l__tblr_a_int
+      { \cs:w g__tblr_table_cmd_ \cs_to_str:N #1 _arg_numb_tl \cs_end: }
+    \tl_put_right:Nn \l__tblr_saved_table_commands_before_cell_text_tl {#1}
+    \int_compare:nNnTF {\l__tblr_a_int} < {0}
+      {
+        \int_set:Nn \l__tblr_a_int { \int_abs:n {\l__tblr_a_int} - 1 }
+        \peek_charcode:NTF [
+          { \__tblr_extract_table_command_arg_o:w }
+          { \__tblr_extract_table_command_arg_next: }
+      }
+      { \__tblr_extract_table_command_arg_next: }
+  }
+
+\cs_new_protected:Npn \__tblr_extract_table_command_arg_o:w [#1]
+  {
+    \tl_put_right:Nn \l__tblr_saved_table_commands_before_cell_text_tl { [#1] }
+    \__tblr_extract_table_command_arg_next:
+  }
+
+\cs_new_protected:Npn \__tblr_extract_table_command_arg_m:n #1
+  {
+    \tl_put_right:Nn \l__tblr_saved_table_commands_before_cell_text_tl { {#1} }
+    \__tblr_extract_table_command_arg_next:
+  }
+
+\cs_new_protected:Npn \__tblr_extract_table_command_arg_next:
+  {
+    \int_compare:nNnTF {\l__tblr_a_int} > {0}
+      {
+        \int_decr:N \l__tblr_a_int
+        \__tblr_extract_table_command_arg_m:n
+      }
+      {
+        \bool_if:NTF \l__tblr_table_command_unbrace_next_bool
+          { \__tblr_last_unbraced:Nn \__tblr_extract_table_commands_next:w }
+          { \__tblr_extract_table_commands_next:w }
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_last_unbraced:Nn #1 #2 { #1 #2 }
+
+%% The outermost set of braces of cell text #1 will be removed
+\cs_new_protected:Npn \__tblr_save_real_cell_text:w #1 \scan_stop:
+  {
+    \tl_set:Nn \l__tblr_saved_cell_text_after_table_commands_tl {#1}
+  }
+
+%%% --------------------------------------------------------
+%%  \section{Initial Table Specifications}
+%%% --------------------------------------------------------
+
+\prop_gset_from_keyval:Nn \g__tblr_default_tblr_table_prop
+  {
+    stretch = 1,
+  }
+
+\prop_gset_from_keyval:Nn \g__tblr_default_tblr_rows_prop
+  {
+    abovesep = 2pt,
+    belowsep = 2pt,
+    @row-height = 0pt,
+    @row-head = 0pt,
+    @row-foot = 0pt,
+    @row-upper = 0pt,
+    @row-lower = 0pt,
+  }
+
+\prop_gset_from_keyval:Nn \g__tblr_default_tblr_columns_prop
+  {
+    leftsep = 6pt,
+    rightsep = 6pt,
+    @col-width = 0pt,
+  }
+
+\prop_gset_from_keyval:Nn \g__tblr_default_tblr_cells_prop
+  {
+    halign = l,
+    valign = t,
+  }
+
+\prop_gset_from_keyval:Nn \g__tblr_default_tblr_hlines_prop
+  {
+    rulesep = 2pt,
+  }
+
+\prop_gset_from_keyval:Nn \g__tblr_default_tblr_vlines_prop
+  {
+    rulesep = 2pt,
+  }
+
+\cs_new_protected:Npn \__tblr_initial_table_spec:
+  {
+    \prop_map_inline:cn { g__tblr_default_ \l__tblr_env_name_tl _table_prop }
+      {
+        \__tblr_prop_gput:nxn { table } { ##1 } {##2}
+      }
+    \int_step_variable:nNn { \c at rowcount } \l__tblr_i_tl
+      {
+        \prop_map_inline:cn { g__tblr_default_ \l__tblr_env_name_tl _rows_prop }
+          {
+            \__tblr_prop_gput:nxn { row } { [\l__tblr_i_tl] / ##1 } {##2}
+          }
+        \prop_map_inline:cn { g__tblr_default_ \l__tblr_env_name_tl _hlines_prop }
+          {
+            \__tblr_prop_gput:nxn { hline } { [\l__tblr_i_tl] / ##1 } {##2}
+          }
+        \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+          {
+            \prop_map_inline:cn
+              { g__tblr_default_ \l__tblr_env_name_tl _cells_prop }
+              {
+                \__tblr_prop_gput:nxn { cell }
+                  { [\l__tblr_i_tl][\l__tblr_j_tl] / ##1 } {##2}
+              }
+          }
+      }
+    \prop_map_inline:cn { g__tblr_default_ \l__tblr_env_name_tl _hlines_prop }
+      {
+        \__tblr_prop_gput:nxn { hline }
+          { [\int_eval:n { \c at rowcount + 1}] / ##1 } {##2}
+      }
+    \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+      {
+        \prop_map_inline:cn { g__tblr_default_ \l__tblr_env_name_tl _columns_prop }
+          {
+            \__tblr_prop_gput:nxn { column } { [\l__tblr_j_tl] / ##1 } {##2}
+          }
+        \prop_map_inline:cn { g__tblr_default_ \l__tblr_env_name_tl _vlines_prop }
+          {
+            \__tblr_prop_gput:nxn { vline } { [\l__tblr_j_tl] / ##1 } {##2}
+          }
+      }
+    \prop_map_inline:cn { g__tblr_default_ \l__tblr_env_name_tl _vlines_prop }
+      {
+        \__tblr_prop_gput:nxn { vline }
+          { [\int_eval:n { \c at colcount + 1}] / ##1 } {##2}
+      }
+    \keys_set:nv { tblr } { l__tblr_default_ \l__tblr_env_name_tl _tl }
+  }
+
+\tl_new:N \l__tblr_default_tblr_tl
+
+%% #1: env name; #2: options
+\NewDocumentCommand \SetTabularrayDefault { O{tblr} m }
+  {
+    \tl_put_right:cn { l__tblr_default_ #1 _tl } { , #2 }
+  }
+\cs_new_eq:NN \SetTblrDefault \SetTabularrayDefault
+
+%%% --------------------------------------------------------
+%%  \section{Parse Table Specifications}
+%%% --------------------------------------------------------
+
+\clist_new:N \g__tblr_table_known_keys_clist
+\clist_gset:Nn \g__tblr_table_known_keys_clist
+  {
+    colspec, rowspec, width, hspan, stretch,
+    column, row, cell, vline, hline, columns, rows, cells, vlines, hlines,
+    leftsep, rightsep, colsep, abovesep, belowsep, rowsep,
+  }
+
+\keys_define:nn { tblr }
+  {
+    colspec .code:n = \__tblr_parse_colrow_spec:nn { column } {#1},
+    rowspec .code:n = \__tblr_parse_colrow_spec:nn { row } {#1},
+    width .code:n = \__tblr_keys_gput:nx { width } { \dim_eval:n {#1} },
+    hspan .code:n = \__tblr_keys_gput:nn { hspan } {#1},
+    stretch .code:n = \__tblr_keys_gput:nn { stretch } {#1},
+    columns .code:n = \__tblr_set_every_column_aux:n {#1},
+    rows    .code:n = \__tblr_set_every_row_aux:n {#1},
+    cells   .code:n = \__tblr_set_every_cell_aux:n {#1},
+    hlines  .code:n = \__tblr_set_every_hline_aux:n {#1},
+    vlines  .code:n = \__tblr_set_every_vline_aux:n {#1},
+    leftsep .code:n = \tblr_set_every_column:nn { } { leftsep = #1 },
+    rightsep .code:n = \tblr_set_every_column:nn { } { rightsep = #1 },
+    colsep .meta:n = { leftsep = #1, rightsep = #1 },
+    abovesep .code:n = \tblr_set_every_row:nn { } { abovesep = #1 },
+    belowsep .code:n = \tblr_set_every_row:nn { } { belowsep = #1 },
+    rowsep .meta:n = { abovesep = #1, belowsep = #1 },
+    unknown .code:n = \__tblr_table_special_key:Vn \l_keys_key_str {#1},
+  }
+
+\regex_const:Nn \c__tblr_split_key_name_regex { ^ ( [a-z] + ) ( . * ) }
+
+\cs_new_protected:Npn \__tblr_table_special_key:nn #1 #2
+  {
+    \regex_extract_once:NnNT \c__tblr_split_key_name_regex {#1} \l_tmpa_seq
+      {
+        \tl_set:Nx \l__tblr_a_tl { \seq_item:Nn \l_tmpa_seq {2} }
+        \tl_set_rescan:Nnx \l__tblr_b_tl {} { \seq_item:Nn \l_tmpa_seq {3} }
+        \cs:w __tblr_set_ \l__tblr_a_tl _aux:Vn \cs_end: \l__tblr_b_tl {#2}
+      }
+  }
+\cs_generate_variant:Nn \__tblr_table_special_key:nn { Vn }
+
+%% If the first key name is known, treat #1 is the table spec;
+%% otherwise, treat #1 as colspec.
+
+\regex_const:Nn \c__tblr_first_key_name_regex { ^ \s * ( [A-Za-z\-] + ) }
+
+\cs_new_protected:Npn \__tblr_parse_table_spec:n #1
+  {
+    \regex_extract_once:NnNTF \c__tblr_first_key_name_regex {#1} \l_tmpa_seq
+      {
+        \clist_if_in:NxTF \g__tblr_table_known_keys_clist
+          { \seq_item:Nn \l_tmpa_seq {2} }
+          { \keys_set:nn { tblr } {#1} }
+          { \__tblr_parse_colrow_spec:nn { column } {#1} }
+      }
+      { \__tblr_parse_colrow_spec:nn { column } {#1} }
+  }
+
+\cs_new_protected:Npn  \__tblr_keys_gput:nn #1 #2
+  {
+    \__tblr_prop_gput:nnn { table } {#1} {#2}
+  }
+\cs_generate_variant:Nn \__tblr_keys_gput:nn { nx }
+
+%%% --------------------------------------------------------
+%%  \section{Typeset and Calculate Sizes}
+%%% --------------------------------------------------------
+
+%% Calculate the width and height for every cell and border
+
+\cs_new_protected:Npn \__tblr_calc_cell_and_line_sizes:
+  {
+    \__tblr_make_strut_box:
+    \__tblr_calculate_line_sizes:
+    \__tblr_calculate_cell_sizes:
+    \LogTblrTracing { cell, row, column, hline, vline }
+    \__tblr_compute_extendable_column_width:
+    \__tblr_adjust_sizes_for_span_cells:
+  }
+
+%% make strut box from stretch option of the table
+
+\box_new:N \l__tblr_strut_ht_box
+\box_new:N \l__tblr_strut_dp_box
+
+\cs_new_protected:Npn \__tblr_make_strut_box:
+  {
+    \tl_set:Nx \l__tblr_s_tl { \__tblr_prop_item:ne { table } { stretch } }
+    \hbox_set:Nn \l__tblr_strut_ht_box
+      { \vrule height \l__tblr_s_tl \box_ht:N \strutbox width ~ 0pt }
+    \hbox_set:Nn \l__tblr_strut_dp_box
+      { \vrule depth \l__tblr_s_tl \box_dp:N \strutbox width ~ 0pt }
+  }
+
+%% Calculate the thickness for every hline and vline
+\cs_new_protected:Npn \__tblr_calculate_line_sizes:
+  {
+    %% We need these two counters in executing hline and vline commands
+    \int_zero:N \c at rownum
+    \int_zero:N \c at colnum
+    \int_step_inline:nn { \c at rowcount + 1 }
+      {
+        \int_incr:N \c at rownum
+        \int_zero:N \c at colnum
+        \int_step_inline:nn { \c at colcount + 1 }
+          {
+            \int_incr:N \c at colnum
+            \int_compare:nNnT { ##1 } < { \c at rowcount + 1 }
+              {
+                \__tblr_measure_and_update_vline_size:nn { ##1 } { ####1 }
+              }
+            \int_compare:nNnT { ####1 } < { \c at colcount + 1 }
+              {
+                \__tblr_measure_and_update_hline_size:nn { ##1 } { ####1 }
+              }
+          }
+      }
+  }
+
+%% Measure and update thickness of the vline
+%% #1: row number, #2 column number
+\cs_new_protected:Npn \__tblr_measure_and_update_vline_size:nn #1 #2
+  {
+    \dim_zero:N \l__tblr_w_dim
+    \tl_set:Nx \l__tblr_n_tl
+      { \__tblr_prop_item:ne { vline } { [#2] / @vline-count } }
+    \tl_if_empty:NF \l__tblr_n_tl
+      {
+        \tl_set:Nx \l__tblr_s_tl
+          { \__tblr_prop_item:ne { vline } { [#2] / rulesep } }
+        \int_step_inline:nn { \l__tblr_n_tl }
+          {
+            \vbox_set_to_ht:Nnn \l__tblr_b_box {1pt}
+              {
+                \__tblr_get_vline_segment_child:nnnnn
+                  {#1} {#2} {##1} {1pt} {1pt}
+              }
+            \tl_set:Nx \l__tblr_w_tl { \dim_eval:n { \box_wd:N \l__tblr_b_box } }
+            \__tblr_prop_gput_if_larger:nxx { vline }
+              { [#2](##1) / @vline-width } { \l__tblr_w_tl }
+            \dim_add:Nn \l__tblr_w_dim { \l__tblr_w_tl }
+            \dim_add:Nn \l__tblr_w_dim { \l__tblr_s_tl }
+          }
+        \dim_add:Nn \l__tblr_w_dim { - \l__tblr_s_tl }
+      }
+    \__tblr_prop_gput_if_larger:nxx { vline }
+      { [#2]/ @vline-width } { \dim_use:N \l__tblr_w_dim }
+  }
+
+%% Get text of a vline segment
+%% #1: row number, #2: column number; #3: index number; #4: height; #5: depth
+%% We put all code inside a group to avoid conflicts of local variables
+\cs_new_protected:Npn \__tblr_get_vline_segment_child:nnnnn #1 #2 #3 #4 #5
+  {
+    \group_begin:
+    \tl_set:Nx \l__tblr_w_tl
+      { \__tblr_prop_item:ne { vline } { [#1][#2](#3) / wd } }
+    \tl_if_empty:NF \l__tblr_w_tl { \dim_set:Nn \rulewidth { \l__tblr_w_tl } }
+    \tl_set:Nx \l__tblr_d_tl
+      { \__tblr_prop_item:ne { vline } { [#1][#2](#3) / @dash } }
+    \tl_set:Nx \l__tblr_a_tl { \tl_head:N \l__tblr_d_tl }
+    \tl_set:Nx \l__tblr_b_tl { \tl_tail:N \l__tblr_d_tl }
+    \exp_args:NV \tl_if_eq:NNTF \l__tblr_a_tl \@tblr at dash
+      {
+        \__tblr_get_vline_dash_style:N \l__tblr_b_tl
+        \xleaders \l__tblr_b_tl \vfil
+      }
+      {
+        \hbox_set:Nn \l__tblr_d_box { \l__tblr_b_tl }
+        \box_set_ht:Nn \l__tblr_d_box {#4}
+        \box_set_dp:Nn \l__tblr_d_box {#5}
+        \box_use:N \l__tblr_d_box
+      }
+    \group_end:
+  }
+\cs_generate_variant:Nn \__tblr_get_vline_segment_child:nnnnn { nnnxx }
+
+%% Measure and update thickness of the hline
+%% #1: row number, #2 column number
+\cs_new_protected:Npn \__tblr_measure_and_update_hline_size:nn #1 #2
+  {
+    \dim_zero:N \l__tblr_h_dim
+    \tl_set:Nx \l__tblr_n_tl
+      { \__tblr_prop_item:ne { hline } { [#1] / @hline-count } }
+    \tl_if_empty:NF \l__tblr_n_tl
+      {
+        \tl_set:Nx \l__tblr_s_tl
+          { \__tblr_prop_item:ne { hline } { [#1] / rulesep } }
+        \int_step_inline:nn { \l__tblr_n_tl }
+          {
+            \hbox_set_to_wd:Nnn \l__tblr_b_box {1pt}
+              { \__tblr_get_hline_segment_child:nnn {#1} {#2} {##1} }
+            \tl_set:Nx \l__tblr_h_tl
+              {
+                \dim_eval:n
+                  { \box_ht:N \l__tblr_b_box + \box_dp:N \l__tblr_b_box }
+              }
+            \__tblr_prop_gput_if_larger:nxx { hline }
+              { [#1](##1) / @hline-height } { \l__tblr_h_tl }
+            \dim_add:Nn \l__tblr_h_dim { \l__tblr_h_tl }
+            \dim_add:Nn \l__tblr_h_dim { \l__tblr_s_tl }
+          }
+        \dim_add:Nn \l__tblr_h_dim { - \l__tblr_s_tl }
+      }
+    \__tblr_prop_gput_if_larger:nxx { hline }
+      { [#1] / @hline-height } { \dim_use:N \l__tblr_h_dim }
+  }
+
+%% Get text of a hline segment
+%% #1: row number, #2: column number; #3: index number
+\cs_new_protected:Npn \__tblr_get_hline_segment_child:nnn #1 #2 #3
+  {
+    \group_begin:
+    \tl_set:Nx \l__tblr_w_tl
+      { \__tblr_prop_item:ne { hline } { [#1][#2](#3) / wd } }
+    \tl_if_empty:NF \l__tblr_w_tl { \dim_set:Nn \rulewidth { \l__tblr_w_tl } }
+    \tl_set:Nx \l__tblr_d_tl
+      { \__tblr_prop_item:ne { hline } { [#1][#2](#3) / @dash } }
+    \tl_set:Nx \l__tblr_a_tl { \tl_head:N \l__tblr_d_tl }
+    \tl_set:Nx \l__tblr_b_tl { \tl_tail:N \l__tblr_d_tl }
+    \exp_args:NV \tl_if_eq:NNTF \l__tblr_a_tl \@tblr at dash
+      {
+        \__tblr_get_hline_dash_style:N \l__tblr_b_tl
+        \xleaders \l__tblr_b_tl \hfil
+      }
+      { \l__tblr_b_tl \hfil }
+    \group_end:
+  }
+
+%% current cell alignments
+\tl_new:N \g__tblr_cell_halign_tl
+\tl_new:N \g__tblr_cell_valign_tl
+\tl_new:N \g__tblr_cell_middle_tl
+
+\tl_const:Nn \c__tblr_valign_h_tl { h }
+\tl_const:Nn \c__tblr_valign_m_tl { m }
+\tl_const:Nn \c__tblr_valign_f_tl { f }
+\tl_const:Nn \c__tblr_valign_t_tl { t }
+\tl_const:Nn \c__tblr_valign_b_tl { b }
+
+\tl_const:Nn \c__tblr_middle_t_tl { t }
+\tl_const:Nn \c__tblr_middle_m_tl { m }
+\tl_const:Nn \c__tblr_middle_b_tl { b }
+
+%% #1: row number; #2: column number
+\cs_new_protected:Npn \__tblr_get_cell_alignments:nn #1 #2
+  {
+    \group_begin:
+    \tl_gset:Nx \g__tblr_cell_halign_tl
+      { \__tblr_prop_item:ne { cell } { [#1][#2] / halign } }
+    \tl_set:Nx \l__tblr_v_tl
+      { \__tblr_prop_item:ne { cell } { [#1][#2] / valign } }
+    \tl_case:NnF \l__tblr_v_tl
+      {
+        \c__tblr_valign_t_tl
+          {
+            \tl_gset:Nn \g__tblr_cell_valign_tl {m}
+            \tl_gset:Nn \g__tblr_cell_middle_tl {t}
+          }
+        \c__tblr_valign_m_tl
+          {
+            \tl_gset:Nn \g__tblr_cell_valign_tl {m}
+            \tl_gset:Nn \g__tblr_cell_middle_tl {m}
+          }
+        \c__tblr_valign_b_tl
+          {
+            \tl_gset:Nn \g__tblr_cell_valign_tl {m}
+            \tl_gset:Nn \g__tblr_cell_middle_tl {b}
+          }
+      }
+      {
+        \tl_gset_eq:NN \g__tblr_cell_valign_tl \l__tblr_v_tl
+        \tl_gclear:N \g__tblr_cell_middle_tl
+      }
+    \group_end:
+  }
+
+%% current cell dimensions
+\dim_new:N \g__tblr_cell_wd_dim
+\dim_new:N \g__tblr_cell_ht_dim
+\dim_new:N \g__tblr_cell_head_dim
+\dim_new:N \g__tblr_cell_foot_dim
+
+%% Calculate the width and height for every cell
+\cs_new_protected:Npn \__tblr_calculate_cell_sizes:
+  {
+    %% You can use these two counters in cell text
+    \int_zero:N \c at rownum
+    \int_zero:N \c at colnum
+    \int_step_inline:nn { \c at rowcount }
+      {
+        \int_incr:N \c at rownum
+        \int_zero:N \c at colnum
+        \tl_set:Nx \l__tblr_h_tl
+           { \__tblr_prop_item:ne { row } { [\int_use:N \c at rownum] / height } }
+        \tl_if_empty:NF \l__tblr_h_tl
+          {
+            \__tblr_prop_gput:nxV { row }
+              { [\int_use:N \c at rownum] / @row-height } \l__tblr_h_tl
+          }
+        \int_step_inline:nn { \c at colcount }
+          {
+            \int_incr:N \c at colnum
+            \__tblr_measure_cell_update_sizes:nnNNNN
+              { \int_use:N \c at rownum }
+              { \int_use:N \c at colnum }
+              \g__tblr_cell_wd_dim
+              \g__tblr_cell_ht_dim
+              \g__tblr_cell_head_dim
+              \g__tblr_cell_foot_dim
+          }
+      }
+  }
+
+%% Measure and update natural dimensions of the row/column/cell
+%% #1: row number; #2 column number; #3: width dimension;
+%% #4: total height dimension; #5: head dimension; #6: foot dimension
+\cs_new_protected:Npn \__tblr_measure_cell_update_sizes:nnNNNN #1 #2 #3 #4 #5 #6
+  {
+    \__tblr_get_cell_alignments:nn {#1} {#2}
+    \hbox_set:Nn \l_tmpa_box { \__tblr_get_cell_text:nn {#1} {#2} }
+    \__tblr_update_cell_size:nnNNNN {#1} {#2} #3 #4 #5 #6
+    \__tblr_update_row_size:nnNNN {#1} {#2} #4 #5 #6
+    \__tblr_update_col_size:nN {#2} #3
+  }
+
+%% #1: row number, #2: column number
+\cs_new_protected:Npn \__tblr_get_cell_text:nn #1 #2
+  {
+    \__tblr_prop_if_in:nxTF {cell} { [#1][#2] / omit }
+      {
+        \dim_gzero:N \g__tblr_cell_wd_dim
+        \dim_gzero:N \g__tblr_cell_ht_dim
+        \dim_gzero:N \g__tblr_cell_head_dim
+        \dim_gzero:N \g__tblr_cell_foot_dim
+      }
+      { \__tblr_get_cell_text_real:nn { #1 } { #2 } }
+  }
+
+%% Get cell text, #1: row number, #2: column number
+%% If the width of the cell is not set, split it with \\ and compute the width
+%% Therefore we always get a vbox for any cell
+\cs_new_protected:Npn \__tblr_get_cell_text_real:nn #1 #2
+  {
+    \group_begin:
+    \tl_set:Nx \l__tblr_c_tl { \__tblr_prop_item:ne {text} {[#1][#2]} }
+    \tl_set:Nx \l__tblr_w_tl
+      { \__tblr_prop_item:ne { cell } { [#1][#2] / width } }
+    \tl_if_empty:NT \l__tblr_w_tl
+      {
+        \__tblr_prop_if_in:nxF { cell } { [#1][#2] / colspan }
+          {
+            \tl_set:Nx \l__tblr_w_tl
+              { \__tblr_prop_item:ne { column } { [#2] / width } }
+          }
+      }
+    \tl_if_empty:NT \l__tblr_w_tl
+      {
+        \bool_if:NTF \l__tblr_math_mode_bool
+          {
+            \hbox_set:Nn \l_tmpa_box { $\l__tblr_c_tl$ }
+            \tl_set:Nx \l__tblr_w_tl { \box_wd:N \l_tmpa_box }
+          }
+          {
+            \tl_set_eq:NN \l_tmpb_tl \l__tblr_c_tl
+            \__tblr_insert_braces:N \l_tmpb_tl
+            \seq_set_split:NnV \l_tmpa_seq { \\ } \l_tmpb_tl
+            \tl_set:Nn \l__tblr_w_tl { 0pt }
+            \seq_map_variable:NNn \l_tmpa_seq \l_tmpa_tl
+              {
+                \__tblr_remove_braces:N \l_tmpa_tl
+                \hbox_set:Nn \l_tmpa_box { \l_tmpa_tl }
+                \tl_set:Nx \l__tblr_w_tl
+                  { \dim_max:nn { \l__tblr_w_tl } { \box_wd:N \l_tmpa_box } }
+              }
+          }
+      }
+    \__tblr_get_vcell_and_sizes:NN \l__tblr_c_tl \l__tblr_w_tl
+    \group_end:
+  }
+
+%% #1: cell text; #2: box width
+\cs_new_protected:Npn \__tblr_get_vcell_and_sizes:NN #1 #2
+  {
+    \group_begin:
+    \vbox_set_top:Nn \l_tmpa_box { \__tblr_make_vcell_text:nN #1 #2 }
+    \vbox_set:Nn \l_tmpb_box { \__tblr_make_vcell_text:nN #1 #2 }
+    \dim_gset:Nn \g__tblr_cell_wd_dim { \box_wd:N \l_tmpb_box }
+    \dim_gset:Nn \g__tblr_cell_ht_dim
+      { \box_ht:N \l_tmpb_box + \box_dp:N \l_tmpb_box }
+    \dim_gset:Nn \g__tblr_cell_head_dim { \box_ht:N \l_tmpa_box }
+    \dim_gset:Nn \g__tblr_cell_foot_dim { \box_dp:N \l_tmpb_box }
+    \tl_case:Nn \g__tblr_cell_valign_tl
+      {
+        \c__tblr_valign_h_tl
+          { \box_use:N \l_tmpa_box }
+        \c__tblr_valign_m_tl
+          {
+            \tl_case:Nn \g__tblr_cell_middle_tl
+              {
+                \c__tblr_middle_t_tl
+                  { \box_use:N \l_tmpa_box }
+                \c__tblr_middle_m_tl
+                  {
+                    \tl_set:Nx \l__tblr_b_tl
+                      {
+                        \dim_eval:n
+                          {
+                            ( \g__tblr_cell_ht_dim - \g__tblr_cell_head_dim
+                                                   - \g__tblr_cell_foot_dim ) / 2
+                          }
+                      }
+                    \box_set_ht:Nn \l_tmpb_box
+                      { \g__tblr_cell_head_dim + \l__tblr_b_tl }
+                    \box_set_dp:Nn \l_tmpb_box
+                      { \g__tblr_cell_foot_dim + \l__tblr_b_tl }
+                    \box_use:N \l_tmpb_box
+                  }
+                \c__tblr_middle_b_tl
+                  { \box_use:N \l_tmpb_box }
+              }
+          }
+        \c__tblr_valign_f_tl
+          { \box_use:N \l_tmpb_box }
+      }
+    \group_end:
+  }
+
+\cs_new_eq:NN \__tlbr_halign_l: \raggedright
+\cs_new_eq:NN \__tlbr_halign_c: \centering
+\cs_new_eq:NN \__tlbr_halign_r: \raggedleft
+
+%% #1: cell text; #2: box width
+\cs_new_protected:Npn \__tblr_make_vcell_text:nN #1 #2
+  {
+    \dim_set:Nn \tex_hsize:D { #2 }
+    \@arrayparboxrestore
+    \cs:w __tlbr_halign_ \g__tblr_cell_halign_tl : \cs_end:
+    \mode_leave_vertical:
+    \box_use:N \l__tblr_strut_ht_box
+    \bool_if:NTF \l__tblr_math_mode_bool { $#1$ } { #1 }
+    \box_use:N \l__tblr_strut_dp_box
+  }
+
+%% #1: total height dimension; #2: head dimension; #3: foot dimension;
+%% #4: tl for resulting upper size; #5: tl for resulting lower size
+
+\tl_new:N \l__tblr_middle_body_tl
+
+\cs_new_protected:Npn \__tblr_get_middle_cell_upper_lower:NNNNN #1 #2 #3 #4 #5
+  {
+    \tl_case:Nn \g__tblr_cell_middle_tl
+      {
+        \c__tblr_middle_t_tl
+          {
+            \tl_set:Nx #4 { \dim_use:N #2 }
+            \tl_set:Nx #5 { \dim_eval:n { #1 - #2 } }
+          }
+        \c__tblr_middle_m_tl
+          {
+            \tl_set:Nx \l__tblr_middle_body_tl { \dim_eval:n { #1 - #2 - #3 } }
+            \tl_set:Nx #4 { \dim_eval:n { #2 + \l__tblr_middle_body_tl / 2 } }
+            \tl_set:Nx #5 { \dim_eval:n { #3 + \l__tblr_middle_body_tl / 2 } }
+          }
+        \c__tblr_middle_b_tl
+          {
+            \tl_set:Nx #4 { \dim_eval:n { #1 - #3 } }
+            \tl_set:Nx #5 { \dim_use:N #3 }
+          }
+      }
+  }
+
+%% Update natural dimensions of the cell
+%% #1: row number; #2 column number; #3: width dimension;
+%% #4: total height dimension; #5: head dimension; #6: foot dimension
+\cs_new_protected:Npn \__tblr_update_cell_size:nnNNNN #1 #2 #3 #4 #5 #6
+  {
+    \group_begin:
+    \tl_set:Nx \l__tblr_c_tl
+      { \__tblr_prop_item:ne {cell} { [#1][#2] / colspan } }
+    \tl_if_empty:NF \l__tblr_c_tl
+      {
+        \__tblr_prop_gput:nxx {cell} { [#1][#2] / @cell-width } { \dim_use:N #3 }
+        \dim_gzero:N #3 % don't affect column width
+      }
+    \tl_set:Nx \l__tblr_r_tl
+      { \__tblr_prop_item:ne {cell} { [#1][#2] / rowspan } }
+    \tl_if_empty:NF \l__tblr_r_tl
+      {
+        \tl_case:Nn \g__tblr_cell_valign_tl
+          {
+            \c__tblr_valign_h_tl
+              {
+                \tl_set:Nx \l__tblr_u_tl { \dim_use:N #5 }
+                \tl_set:Nx \l__tblr_v_tl { \dim_eval:n { #4 - #5 } }
+                %% Update the head size of the first span row here
+                \__tblr_prop_gput_if_larger:nxx { row }
+                  { [#1] / @row-head } { \dim_use:N #5 }
+              }
+            \c__tblr_valign_f_tl
+              {
+                \tl_set:Nx \l__tblr_u_tl { \dim_eval:n { #4 - #6 } }
+                \tl_set:Nx \l__tblr_v_tl { \dim_use:N #6 }
+                %% Update the foot size of the last span row here
+                \__tblr_prop_gput_if_larger:nxx { row }
+                  { [\int_eval:n { #1 + \l__tblr_r_tl - 1 }] / @row-foot }
+                  { \dim_use:N #6 }
+              }
+            \c__tblr_valign_m_tl
+              {
+                \__tblr_get_middle_cell_upper_lower:NNNNN
+                  #4 #5 #6 \l__tblr_u_tl \l__tblr_v_tl
+              }
+          }
+        \__tblr_prop_gput:nxV {cell} { [#1][#2] / @cell-height } \l__tblr_u_tl
+        \__tblr_prop_gput:nxV {cell} { [#1][#2] / @cell-depth } \l__tblr_v_tl
+        %% Don't affect row sizes
+        \dim_gzero:N #4
+        \dim_gzero:N #5
+        \dim_gzero:N #6
+      }
+    \group_end:
+  }
+
+
+%% Update size of the row. #1: row number; #2: column number;
+%% #3: total height dimension; #4: head dimension; #5: foot dimension
+\cs_new_protected:Npn \__tblr_update_row_size:nnNNN #1 #2 #3 #4 #5
+  {
+    \group_begin:
+    %% Note that \l__tblr_h_tl may be empty
+    \tl_set:Nx \l__tblr_h_tl
+      { \__tblr_prop_item:ne { row } { [#1] / @row-height } }
+    \tl_if_eq:NNTF \g__tblr_cell_valign_tl \c__tblr_valign_m_tl
+      {
+        \tl_set:Nx \l__tblr_a_tl
+          { \__tblr_prop_item:ne { row } { [#1] / @row-upper } }
+        \tl_set:Nx \l__tblr_b_tl
+          { \__tblr_prop_item:ne { row } { [#1] / @row-lower } }
+        \__tblr_get_middle_cell_upper_lower:NNNNN
+          #3 #4 #5 \l__tblr_u_tl \l__tblr_v_tl
+        \dim_compare:nNnT { \l__tblr_u_tl } > { \l__tblr_a_tl }
+          {
+            \tl_set_eq:NN \l__tblr_a_tl \l__tblr_u_tl
+            \__tblr_prop_gput:nxV { row }
+              { [#1] / @row-upper } \l__tblr_a_tl
+          }
+        \dim_compare:nNnT { \l__tblr_v_tl } > { \l__tblr_b_tl }
+          {
+            \tl_set_eq:NN \l__tblr_b_tl \l__tblr_v_tl
+            \__tblr_prop_gput:nxV { row }
+              { [#1] / @row-lower } \l__tblr_b_tl
+          }
+        \dim_compare:nNnT
+          { \l__tblr_a_tl + \l__tblr_b_tl } > { \l__tblr_h_tl + 0pt }
+          {
+            \__tblr_prop_gput:nxx { row } { [#1] / @row-height }
+              { \dim_eval:n { \l__tblr_a_tl + \l__tblr_b_tl } }
+          }
+      }
+      {
+        \tl_set:Nx \l__tblr_e_tl
+          { \__tblr_prop_item:ne { row } { [#1] / @row-head } }
+        \tl_set:Nx \l__tblr_f_tl
+          { \__tblr_prop_item:ne { row } { [#1] / @row-foot } }
+        \dim_compare:nNnT {#4} > {\l__tblr_e_tl}
+          {
+            \__tblr_prop_gput:nxx { row } { [#1] / @row-head } { \dim_use:N #4 }
+          }
+        \dim_compare:nNnT {#5} > {\l__tblr_f_tl}
+          {
+            \__tblr_prop_gput:nxx { row } { [#1] / @row-foot } { \dim_use:N #5 }
+          }
+        \tl_set:Nx \l__tblr_x_tl { \dim_max:nn {#4} { \l__tblr_e_tl } }
+        \tl_set:Nx \l__tblr_y_tl { \dim_max:nn {#5} { \l__tblr_f_tl } }
+        \dim_compare:nNnT
+          { #3 - #4 - #5 } > { \l__tblr_h_tl - \l__tblr_x_tl - \l__tblr_y_tl }
+          {
+            \__tblr_prop_gput:nxx { row } { [#1] / @row-height }
+              {
+                \dim_eval:n
+                  {
+                    \l__tblr_x_tl
+                    + \dim_use:N #3 - \dim_use:N #4 - \dim_use:N #5
+                    + \l__tblr_y_tl
+                  }
+              }
+          }
+      }
+    \group_end:
+  }
+
+
+%% Update size of the column. #1: column number; #2: width dimension
+
+\cs_new_protected:Npn \__tblr_update_col_size:nN #1 #2
+  {
+    \tl_set:Nx \l_tmpb_tl
+      { \__tblr_prop_item:ne {column} { [#1] / @col-width } }
+    \bool_lazy_or:nnT
+      { \tl_if_empty_p:N \l_tmpb_tl }
+      { \dim_compare_p:nNn { \dim_use:N #2 } > { \l_tmpb_tl } }
+      {
+        \__tblr_prop_gput:nxx {column} { [#1] / @col-width } { \dim_use:N #2 }
+      }
+  }
+
+%%% --------------------------------------------------------
+%%  \section{Calculate and Adjust Extendable Columns}
+%%% --------------------------------------------------------
+
+%% Compute column widths when there are some extendable columns
+
+\dim_new:N \l__column_target_dim
+\prop_new:N \l__column_coefficient_prop
+\prop_new:N \l__column_natural_width_prop
+\prop_new:N \l__column_computed_width_prop
+
+\msg_new:nnn { tabularray } { table-width-too-small }
+  { Table ~ width ~ is ~ too ~ small, need ~ #1 ~ more! }
+
+\cs_new_protected:Npn \__tblr_compute_extendable_column_width:
+  {
+    \__tblr_collect_extendable_column_width:
+    \dim_compare:nNnTF { \l__column_target_dim } > { 0pt }
+      {
+        \prop_if_empty:NF \l__column_coefficient_prop
+          { \__tblr_adjust_extendable_column_width: }
+      }
+      {
+        \msg_warning:nnx { tabularray } { table-width-too-small }
+          { \dim_abs:n { \l__column_target_dim } }
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_collect_extendable_column_width:
+  {
+    \tl_set:Nx \l_tmpa_tl { \__tblr_prop_item:nn {table} {width} }
+    \tl_if_empty:NTF \l_tmpa_tl
+      { \dim_set_eq:NN \l__column_target_dim \linewidth }
+      { \dim_set:Nn \l__column_target_dim { \l_tmpa_tl } }
+    \prop_clear:N \l__column_coefficient_prop
+    \prop_clear:N \l__column_natural_width_prop
+    \prop_clear:N \l__column_computed_width_prop
+    \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+      {
+        \tl_set:Nx \l__tblr_a_tl
+          { \__tblr_prop_item:ne {column} { [\l__tblr_j_tl] / width } }
+        \tl_set:Nx \l__tblr_b_tl
+          { \__tblr_prop_item:ne {column} { [\l__tblr_j_tl] / coefficient } }
+        \tl_set:Nx \l__tblr_c_tl
+          { \__tblr_prop_item:ne {column} { [\l__tblr_j_tl] / @col-width } }
+        \tl_if_empty:NTF \l__tblr_a_tl
+          {
+            \tl_if_empty:NTF \l__tblr_b_tl
+              { \dim_sub:Nn \l__column_target_dim { \l__tblr_c_tl } }
+              {
+                \prop_put:Nxx \l__column_coefficient_prop
+                  { \l__tblr_j_tl } { \l__tblr_b_tl }
+                \prop_put:Nxn \l__column_computed_width_prop
+                  { \l__tblr_j_tl } { 0pt }
+                \dim_compare:nNnF { \l__tblr_b_tl pt } > { 0pt }
+                  {
+                    \prop_put:Nxx \l__column_natural_width_prop
+                      { \l__tblr_j_tl } { \l__tblr_c_tl }
+                  }
+              }
+          }
+          { \dim_sub:Nn \l__column_target_dim { \l__tblr_a_tl } }
+        \tl_set:Nx \l__tblr_a_tl
+          { \__tblr_prop_item:ne {vline} { [\l__tblr_j_tl] / @vline-width } }
+        \tl_set:Nx \l__tblr_b_tl
+          { \__tblr_prop_item:ne {column} { [\l__tblr_j_tl] / leftsep} }
+        \tl_set:Nx \l__tblr_c_tl
+          { \__tblr_prop_item:ne {column} { [\l__tblr_j_tl] / rightsep } }
+        \dim_set:Nn \l__column_target_dim
+          { \l__column_target_dim - \l__tblr_a_tl - \l__tblr_b_tl - \l__tblr_c_tl }
+      }
+    \tl_set:Nx \l__tblr_a_tl
+      {
+        \__tblr_prop_item:ne {vline}
+          { [\int_eval:n {\c at colcount + 1}] / @vline-width }
+      }
+    \tl_if_empty:NF \l__tblr_a_tl
+      { \dim_sub:Nn \l__column_target_dim { \l__tblr_a_tl } }
+    \LogTblrTracing { target }
+  }
+
+%% If all columns have negative coefficients and small natural widths,
+%% \l__column_coefficient_prop will be empty after one or more rounds
+\cs_new_protected:Npn \__tblr_adjust_extendable_column_width:
+  {
+    \bool_while_do:nn
+      { \dim_compare_p:nNn { \l__column_target_dim } > { \hfuzz } }
+      {
+        \prop_if_empty:NTF \l__column_coefficient_prop
+          { \__tblr_adjust_extendable_column_width_negative: }
+          { \__tblr_adjust_extendable_column_width_once: }
+      }
+    \prop_map_inline:Nn \l__column_computed_width_prop
+      {
+        \__tblr_prop_gput:nnx {column} { [##1] / width } { ##2 }
+        \__tblr_prop_gput:nnn {column} { [##1] / @col-width } { 0pt }
+      }
+    \__tblr_calculate_cell_sizes:
+  }
+
+%% We use dimen register, since the coefficient may be a decimal number
+\cs_new_protected:Npn \__tblr_adjust_extendable_column_width_once:
+  {
+    \dim_zero:N \l_tmpa_dim
+    \prop_map_inline:Nn \l__column_coefficient_prop
+      {
+        \dim_add:Nn \l_tmpa_dim { \dim_abs:n { ##2 pt } }
+      }
+    \tl_set:Nx \l__tblr_w_tl
+      { \dim_ratio:nn { \l__column_target_dim } { \l_tmpa_dim } }
+    \dim_zero:N \l__column_target_dim
+    \prop_map_inline:Nn \l__column_coefficient_prop
+      {
+        \tl_set:Nx \l__tblr_a_tl
+          { \dim_eval:n { \dim_abs:n { ##2 pt } * \l__tblr_w_tl } }
+        \dim_compare:nNnTF { ##2 pt } > { 0pt }
+          {
+            \__tblr_add_dimen_value:Nnn
+              \l__column_computed_width_prop { ##1 } { \l__tblr_a_tl }
+          }
+          {
+            \tl_set:Nx \l__tblr_b_tl
+              { \prop_item:Nn \l__column_natural_width_prop { ##1 } }
+            \tl_set:Nx \l__tblr_c_tl
+              { \prop_item:Nn \l__column_computed_width_prop { ##1 } }
+            \dim_compare:nNnTF { \l__tblr_a_tl + \l__tblr_c_tl } > { \l__tblr_b_tl }
+              {
+                \prop_put:Nnx \l__column_computed_width_prop
+                  { ##1 } { \l__tblr_b_tl }
+                \dim_add:Nn \l__column_target_dim
+                  { \l__tblr_a_tl + \l__tblr_c_tl - \l__tblr_b_tl }
+                \prop_remove:Nn \l__column_coefficient_prop { ##1 }
+              }
+              {
+                \__tblr_add_dimen_value:Nnn
+                  \l__column_computed_width_prop { ##1 } { \l__tblr_a_tl }
+              }
+          }
+      }
+    \LogTblrTracing { target }
+  }
+
+\cs_new_protected:Npn \__tblr_adjust_extendable_column_width_negative:
+  {
+    \dim_zero:N \l_tmpa_dim
+    \prop_map_inline:Nn \l__column_natural_width_prop
+      { \dim_add:Nn \l_tmpa_dim { ##2 } }
+    \tl_set:Nx \l_tmpa_tl
+      { \dim_ratio:nn { \l__column_target_dim } { \l_tmpa_dim } }
+    \dim_zero:N \l__column_target_dim
+    \prop_map_inline:Nn \l__column_natural_width_prop
+      {
+        \tl_set:Nx \l_tmpb_tl { \dim_eval:n { ##2 * \l_tmpa_tl } }
+        \__tblr_add_dimen_value:Nnn
+          \l__column_computed_width_prop { ##1 } { \l_tmpb_tl }
+      }
+    \LogTblrTracing { target }
+  }
+
+%%% --------------------------------------------------------
+%%  \section{Calculate and Adjust Multispan Cells}
+%%% --------------------------------------------------------
+
+%% Compute and adjust widths when there are some span cells.
+%% By default, we will compute column widths from span widths;
+%% but if we set table option "hspan = minimal",
+%% we will compute span widths from column widths.
+
+\cs_new_protected:Npn \__tblr_adjust_sizes_for_span_cells:
+  {
+    \__tblr_prop_if_in:nnT {table} {colspan}
+      {
+        \__tblr_collect_column_widths_skips:
+        \str_if_eq:xnTF
+          { \__tblr_prop_item:ne {table} {hspan} } {minimal}
+          {
+            \__tblr_set_span_widths_from_column_widths:
+          }
+          {
+            \__tblr_collect_span_widths:
+            \__tblr_set_column_widths_from_span_widths:
+          }
+        \LogTblrTracing {column}
+        \__tblr_calculate_cell_sizes:
+      }
+    \__tblr_prop_if_in:nnT {table} {rowspan}
+      {
+        \__tblr_collect_row_heights_skips:
+        \__tblr_collect_span_heights:
+        \__tblr_set_row_heights_from_span_heights:
+        \LogTblrTracing {row}
+      }
+  }
+
+\prop_new:N \l__tblr_col_item_skip_size_prop
+\prop_new:N \l__tblr_col_span_size_prop
+\prop_new:N \l__tblr_row_item_skip_size_prop
+\prop_new:N \l__tblr_row_span_size_prop
+
+\cs_new_protected:Npn \__tblr_collect_column_widths_skips:
+  {
+    \prop_clear:N \l__tblr_col_item_skip_size_prop
+    \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+      {
+        \int_compare:nNnTF { \l__tblr_j_tl } > { 1 }
+          {
+            \prop_put:Nxx \l__tblr_col_item_skip_size_prop { skip[\l__tblr_j_tl] }
+              {
+                \dim_eval:n
+                  {
+                    \__tblr_prop_item:ne {column}
+                      { [\int_eval:n { \l__tblr_j_tl - 1 }] / rightsep }
+                    +
+                    \__tblr_prop_item:ne {vline}
+                      { [\l__tblr_j_tl] / @vline-width }
+                    +
+                    \__tblr_prop_item:ne {column}
+                      { [\l__tblr_j_tl] / leftsep}
+                  }
+              }
+          }
+          {
+            \prop_put:Nxn \l__tblr_col_item_skip_size_prop { skip[\l__tblr_j_tl] }
+              { 0pt }
+          }
+        \prop_put:Nxx \l__tblr_col_item_skip_size_prop { item[\l__tblr_j_tl] }
+          { \__tblr_prop_item:ne {column} { [\l__tblr_j_tl] / @col-width } }
+      }
+    \__tblr_do_if_tracing:nn { cellspan }
+      { \prop_log:N \l__tblr_col_item_skip_size_prop }
+  }
+
+\cs_new_protected:Npn \__tblr_collect_row_heights_skips:
+  {
+    \prop_clear:N \l__tblr_row_item_skip_size_prop
+    \int_step_variable:nNn { \c at rowcount } \l__tblr_i_tl
+      {
+        \int_compare:nNnTF { \l__tblr_i_tl } > { 1 }
+          {
+            \prop_put:Nxx \l__tblr_row_item_skip_size_prop { skip[\l__tblr_i_tl] }
+              {
+                \dim_eval:n
+                  {
+                    \__tblr_prop_item:ne {row}
+                      { [\int_eval:n {\l__tblr_i_tl - 1}] / belowsep }
+                    +
+                    \__tblr_prop_item:ne {hline}
+                      { [\l__tblr_i_tl] / @hline-height }
+                    +
+                    \__tblr_prop_item:ne {row}
+                      { [\l__tblr_i_tl] / abovesep }
+                  }
+              }
+          }
+          {
+            \prop_put:Nxn \l__tblr_row_item_skip_size_prop { skip[\l__tblr_i_tl] }
+              { 0pt }
+          }
+        \__tblr_collect_one_row_height:NN \l__tblr_i_tl \l__tblr_h_tl
+        \prop_put:Nxx \l__tblr_row_item_skip_size_prop
+          { item[\l__tblr_i_tl] } { \l__tblr_h_tl }
+      }
+    \__tblr_do_if_tracing:nn { cellspan }
+      { \prop_log:N \l__tblr_row_item_skip_size_prop }
+  }
+
+%% #1: row number; #2: tl with result
+\cs_new_protected:Npn \__tblr_collect_one_row_height:NN #1 #2
+  {
+    \tl_set:Nx #2 { \__tblr_prop_item:ne { row } { [#1] / @row-height } }
+  }
+
+\cs_new_protected:Npn \__tblr_collect_span_widths:
+  {
+    \prop_clear:N \l__tblr_col_span_size_prop
+    \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+      {
+        \int_step_variable:nNn { \c at rowcount } \l__tblr_i_tl
+          {
+            \tl_set:Nx \l__tblr_a_tl
+              {
+                \__tblr_prop_item:ne {cell}
+                  { [\l__tblr_i_tl][\l__tblr_j_tl] / colspan }
+              }
+            \tl_if_empty:NF \l__tblr_a_tl
+              {
+                \__tblr_put_if_larger:Nxx \l__tblr_col_span_size_prop
+                  {
+                    ( \l__tblr_j_tl -
+                      \int_eval:n {\l__tblr_j_tl + \l__tblr_a_tl - 1} )
+                  }
+                  {
+                    \__tblr_prop_item:ne {cell}
+                      { [\l__tblr_i_tl][\l__tblr_j_tl] / @cell-width }
+                  }
+              }
+          }
+      }
+    \__tblr_do_if_tracing:nn { cellspan }
+      { \prop_log:N \l__tblr_col_span_size_prop }
+  }
+
+\prop_new:N \l__tblr_row_span_to_row_prop
+
+\cs_new_protected:Npn \__tblr_collect_span_heights:
+  {
+    \prop_clear:N \l__tblr_row_span_to_row_prop
+    \prop_clear:N \l__tblr_row_span_size_prop
+    \int_step_variable:nNn { \c at rowcount } \l__tblr_i_tl
+      {
+        \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+          {
+            \tl_set:Nx \l__tblr_a_tl
+              {
+                \__tblr_prop_item:ne {cell}
+                  { [\l__tblr_i_tl][\l__tblr_j_tl] / rowspan }
+              }
+            \tl_if_empty:NF \l__tblr_a_tl
+              {
+                \tl_set:Nx \l__tblr_v_tl
+                  {
+                    \__tblr_prop_item:ne {cell}
+                      { [\l__tblr_i_tl][\l__tblr_j_tl] / valign }
+                  }
+                \tl_if_eq:NnT \l__tblr_v_tl { h }
+                  {
+                    \tl_set:Nx \l__tblr_h_tl
+                      {
+                        \__tblr_prop_item:ne {row}
+                          { [\l__tblr_i_tl] / @row-head }
+                      }
+                    \__tblr_prop_gput:nxV {cell}
+                      { [\l__tblr_i_tl][\l__tblr_j_tl] / @cell-height }
+                      \l__tblr_h_tl
+                  }
+                \tl_if_eq:NnT \l__tblr_v_tl { f }
+                  {
+                    \tl_set:Nx \l__tblr_d_tl
+                      {
+                        \__tblr_prop_item:ne {row}
+                          {
+                            [\int_eval:n {\l__tblr_i_tl + \l__tblr_a_tl - 1}]
+                            / @row-foot
+                          }
+                      }
+                    \__tblr_prop_gput:nxV {cell}
+                      { [\l__tblr_i_tl][\l__tblr_j_tl] / @cell-depth }
+                      \l__tblr_d_tl
+                  }
+                \__tblr_put_if_larger:Nxx \l__tblr_row_span_size_prop
+                  {
+                    ( \l__tblr_i_tl -
+                      \int_eval:n {\l__tblr_i_tl + \l__tblr_a_tl - 1} )
+                  }
+                  {
+                    \dim_eval:n
+                      {
+                        \__tblr_prop_item:ne {cell}
+                          { [\l__tblr_i_tl][\l__tblr_j_tl] / @cell-height }
+                        +
+                        \__tblr_prop_item:ne {cell}
+                          { [\l__tblr_i_tl][\l__tblr_j_tl] / @cell-depth }
+                      }
+                  }
+                \prop_put:Nxx \l__tblr_row_span_to_row_prop
+                  { [\l__tblr_i_tl][\l__tblr_j_tl] }
+                  { \int_eval:n {\l__tblr_i_tl + \l__tblr_a_tl - 1} }
+              }
+          }
+      }
+    \__tblr_do_if_tracing:nn { cellspan }
+      {
+        \prop_log:N \l__tblr_row_span_to_row_prop
+        \prop_log:N \l__tblr_row_span_size_prop
+      }
+  }
+
+%% Compute and set column widths from span widths
+\cs_new_protected:Npn \__tblr_set_column_widths_from_span_widths:
+  {
+    \__tblr_calc_item_sizes_from_span_sizes:xNN
+      { \int_use:N \c at colcount }
+      \l__tblr_col_item_skip_size_prop
+      \l__tblr_col_span_size_prop
+    \__tblr_set_all_column_widths:
+  }
+
+%% Compute and set row heights from span heights
+\cs_new_protected:Npn \__tblr_set_row_heights_from_span_heights:
+  {
+    \__tblr_calc_item_sizes_from_span_sizes:xNN
+      { \int_use:N \c at rowcount }
+      \l__tblr_row_item_skip_size_prop
+      \l__tblr_row_span_size_prop
+    \__tblr_set_all_row_heights:
+  }
+
+%% See page 245 in Chapter 22 of TeXbook
+%% #1: total number of items
+%% #2: prop list with item sizes and skip sizes; #3: prop list with span sizes
+\cs_new_protected:Npn \__tblr_calc_item_sizes_from_span_sizes:nNN #1 #2 #3
+  {
+    \int_step_variable:nNn { #1 } \l__tblr_j_tl
+      {
+        \dim_set:Nn \l__tblr_w_dim
+          {
+            \prop_item:Ne #2 { item[\l__tblr_j_tl] }
+          }
+        \int_step_variable:nNn { \l__tblr_j_tl - 1 } \l__tblr_i_tl
+          {
+            \tl_set:Nx \l__tblr_a_tl
+              { \prop_item:Ne #3 { (\l__tblr_i_tl-\l__tblr_j_tl) } }
+            \tl_if_empty:NF \l__tblr_a_tl
+              {
+                \int_step_variable:nnNn
+                  { \l__tblr_i_tl } { \l__tblr_j_tl - 1 } \l__tblr_k_tl
+                  {
+                    \__tblr_do_if_tracing:nn { cellspan }
+                      {
+                        \tl_log:x
+                          { \l__tblr_j_tl : \l__tblr_i_tl -> \l__tblr_k_tl }
+                      }
+                    \tl_set:Nx \l_tmpa_tl
+                      {
+                        \prop_item:Ne #2 { itemskip[\l__tblr_k_tl] }
+                      }
+                    \tl_set:Nx \l__tblr_a_tl
+                      { \dim_eval:n { \l__tblr_a_tl - \l_tmpa_tl } }
+                  }
+                \dim_compare:nNnT { \l__tblr_a_tl } > { \l__tblr_w_dim }
+                  {
+                    \dim_set:Nn \l__tblr_w_dim { \l__tblr_a_tl }
+                  }
+              }
+          }
+        \prop_put:Nxx #2
+          { item[\l__tblr_j_tl] } { \dim_use:N \l__tblr_w_dim }
+        \int_compare:nNnT { \l__tblr_j_tl } < { #1 }
+          {
+            \tl_set:Nx \l_tmpb_tl
+              {
+                \prop_item:Ne #2
+                  { skip[\int_eval:n { \l__tblr_j_tl + 1} ] }
+              }
+            \dim_add:Nn \l__tblr_w_dim { \l_tmpb_tl }
+            \prop_put:Nxx #2
+              { itemskip[\l__tblr_j_tl] } { \dim_use:N \l__tblr_w_dim }
+          }
+      }
+    \__tblr_do_if_tracing:nn { cellspan } { \prop_log:N #2 }
+  }
+\cs_generate_variant:Nn \__tblr_calc_item_sizes_from_span_sizes:nNN { x }
+
+\cs_new_protected:Npn \__tblr_set_all_column_widths:
+  {
+    \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+      {
+        \__tblr_prop_gput:nxx {column}
+          { [\l__tblr_j_tl] / @col-width }
+          { \prop_item:Ne \l__tblr_col_item_skip_size_prop { item[\l__tblr_j_tl] } }
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_set_all_row_heights:
+  {
+    \int_step_variable:nNn { \c at rowcount } \l__tblr_i_tl
+      {
+        \tl_set:Nx \l__tblr_h_tl
+          {
+            \__tblr_prop_item:ne {row} { [\l__tblr_i_tl] / @row-head }
+          }
+        \tl_set:Nx \l__tblr_d_tl
+          {
+            \__tblr_prop_item:ne {row} { [\l__tblr_i_tl] / @row-foot }
+          }
+        \tl_set:Nx \l__tblr_a_tl
+          {
+            \prop_item:Ne \l__tblr_row_item_skip_size_prop { item[\l__tblr_i_tl] }
+          }
+        \__tblr_collect_one_row_height:NN \l__tblr_i_tl \l__tblr_t_tl
+        \__tblr_prop_gput:nxx {row}
+          { [\l__tblr_i_tl] / @row-height } { \l__tblr_a_tl }
+      }
+  }
+
+\cs_new_protected:Npn \__tblr_get_span_key_row_col:w [#1][#2]
+  {
+    \tl_set:Nn \l__tblr_i_tl {#1}
+    \tl_set:Nn \l__tblr_j_tl {#2}
+  }
+
+%% Compute and set span widths from column widths
+\cs_new_protected:Npn \__tblr_set_span_widths_from_column_widths:
+  {
+    \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+      {
+        \int_step_variable:nNn { \c at rowcount } \l__tblr_i_tl
+          {
+            \tl_set:Nx \l__tblr_a_tl
+              {
+                \__tblr_prop_item:ne {cell}
+                  { [\l__tblr_i_tl][\l__tblr_j_tl] / colspan }
+              }
+            \tl_if_empty:NF \l__tblr_a_tl
+              {
+                \__tblr_calc_span_widths:xxN
+                  { \l__tblr_j_tl }
+                  { \int_eval:n { \l__tblr_j_tl + \l__tblr_a_tl - 1 } }
+                  \l__tblr_w_dim
+                \__tblr_prop_gput:nxx {cell}
+                  { [\l__tblr_i_tl][\l__tblr_j_tl] / width }
+                  { \dim_use:N \l__tblr_w_dim }
+              }
+          }
+      }
+  }
+
+%% Cell is spanned from col #1 to col #2, #3 is the return dim
+\cs_new_protected:Npn \__tblr_calc_span_widths:nnN #1 #2 #3
+  {
+    \dim_zero:N #3
+    \int_step_inline:nnn { #1 } { #2 }
+      {
+        \tl_set:Nx \l_tmpa_tl
+          { \prop_item:Ne \l__tblr_col_item_skip_size_prop { skip[##1] } }
+        \tl_set:Nx \l_tmpb_tl
+          { \prop_item:Ne \l__tblr_col_item_skip_size_prop { item[##1] } }
+        \dim_add:Nn #3 { \dim_eval:n { \l_tmpa_tl + \l_tmpb_tl } }
+      }
+  }
+\cs_generate_variant:Nn \__tblr_calc_span_widths:nnN { xxN }
+
+%%% --------------------------------------------------------
+%%  \section{Build the Whole Table}
+%%% --------------------------------------------------------
+
+\tl_new:N \__tlbr_vbox_align_tl
+\tl_const:Nn \__tlbr_vbox_t_tl {t}
+\tl_const:Nn \__tlbr_vbox_m_tl {m}
+\tl_const:Nn \__tlbr_vbox_c_tl {c}
+\tl_const:Nn \__tlbr_vbox_b_tl {b}
+
+\box_new:N \l__tblr_table_box
+
+\cs_new_protected:Npn \__tblr_build_whole:n #1
+  {
+    \vbox_set:Nn \l__tblr_table_box
+      {
+        \int_step_variable:nNn { \c at rowcount } \l__tblr_i_tl
+          {
+            \hbox:n { \__tblr_build_hline:V \l__tblr_i_tl }
+            \hrule height ~ 0pt % remove lineskip between hlines and rows
+            \hbox:n { \__tblr_build_row:N \l__tblr_i_tl }
+            \hrule height ~ 0pt
+          }
+        \hbox:n { \__tblr_build_hline:n { \int_eval:n {\c at rowcount + 1} } }
+      }
+    \__tblr_valign_whole:Nn \l__tblr_table_box #1
+  }
+
+\cs_new_protected:Npn \__tblr_valign_whole:Nn #1 #2
+  {
+    \group_begin:
+    \tl_set:Nn \__tlbr_vbox_align_tl {#2}
+    \dim_set:Nn \l__tblr_t_dim { \box_ht:N #1 + \box_dp:N #1 }
+    \tl_case:NnF \__tlbr_vbox_align_tl
+      {
+        \__tlbr_vbox_m_tl
+          { \__tblr_valign_whole_middle:N #1 }
+        \__tlbr_vbox_c_tl
+          { \__tblr_valign_whole_middle:N #1 }
+        \__tlbr_vbox_t_tl
+          { \__tblr_valign_whole_top:N #1 }
+        \__tlbr_vbox_b_tl
+          { \__tblr_valign_whole_bottom:N #1 }
+      }
+      { \__tblr_valign_whole_middle:N #1 }
+    \group_end:
+  }
+
+\cs_new_protected:Npn \__tblr_valign_whole_middle:N #1
+  {
+    \hbox:n { $ \m at th \tex_vcenter:D { \vbox_unpack_drop:N #1 } $ }
+  }
+
+\cs_new_protected:Npn \__tblr_valign_whole_top:N #1
+  {
+    \tl_set:Nx \l__tblr_a_tl
+      { \__tblr_prop_item:ne { hline } { [1] / @hline-height } }
+    %% Note that \l__tblr_b_tl may be empty
+    \tl_set:Nx \l__tblr_b_tl
+      { \__tblr_prop_item:ne { table } { baseline } }
+    \bool_lazy_or:nnTF
+      { \dim_compare_p:nNn { \l__tblr_a_tl } = { 0pt } }
+      { \int_compare_p:nNn { \l__tblr_b_tl + 0 } = { 1 } }
+      {
+        \dim_set:Nn \l__tblr_h_dim
+          {
+            \__tblr_prop_item:ne { row } { [1] / abovesep }
+            +
+            ( \__tblr_prop_item:ne { row } { [1] / @row-height }
+              +
+              \__tblr_prop_item:ne { row } { [1] / @row-upper }
+              -
+              \__tblr_prop_item:ne { row } { [1] / @row-lower }
+            ) / 2
+          }
+        \dim_set:Nn \l__tblr_d_dim { \l__tblr_t_dim - \l__tblr_h_dim }
+      }
+      {
+        \dim_set:Nn \l__tblr_h_dim { 0pt }
+        \dim_set_eq:NN \l__tblr_d_dim \l__tblr_t_dim
+      }
+    \box_set_ht:Nn #1 { \l__tblr_h_dim }
+    \box_set_dp:Nn #1 { \l__tblr_d_dim }
+    \box_use_drop:N #1
+  }
+
+\cs_new_protected:Npn \__tblr_valign_whole_bottom:N #1
+  {
+    \tl_set:Nx \l__tblr_a_tl
+      {
+        \__tblr_prop_item:ne { hline }
+          { [\int_eval:n {\c at rowcount + 1}] / @hline-height }
+      }
+    %% Note that \l__tblr_b_tl may be empty
+    \tl_set:Nx \l__tblr_b_tl
+      { \__tblr_prop_item:ne { table } { baseline } }
+    \bool_lazy_or:nnTF
+      { \dim_compare_p:nNn { \l__tblr_a_tl } = { 0pt } }
+      { \int_compare_p:nNn { \l__tblr_b_tl + 0 } = { \c at rowcount } }
+      {
+        \dim_set:Nn \l__tblr_d_dim
+          {
+            ( \__tblr_prop_item:ne { row }
+                { [\int_use:N \c at rowcount] / @row-height }
+              -
+              \__tblr_prop_item:ne { row }
+                { [\int_use:N \c at rowcount] / @row-upper }
+              +
+              \__tblr_prop_item:ne { row }
+                { [\int_use:N \c at rowcount] / @row-lower }
+            ) / 2
+            +
+            \__tblr_prop_item:ne { row } { [1] / belowsep }
+          }
+        \dim_set:Nn \l__tblr_h_dim { \l__tblr_t_dim - \l__tblr_d_dim }
+      }
+      {
+        \dim_set:Nn \l__tblr_d_dim { 0pt }
+        \dim_set_eq:NN \l__tblr_h_dim \l__tblr_t_dim
+      }
+    \box_set_ht:Nn #1 { \l__tblr_h_dim }
+    \box_set_dp:Nn #1 { \l__tblr_d_dim }
+    \box_use_drop:N #1
+  }
+
+\dim_new:N \l__tblr_col_o_wd_dim
+\dim_new:N \l__tblr_col_b_wd_dim
+
+%% Build hline. #1: row number
+\cs_new_protected:Npn \__tblr_build_hline:n #1
+  {
+    \int_step_inline:nn { \c at colcount }
+      { \__tblr_build_hline_segment:nn { #1 } { ##1 } }
+  }
+\cs_generate_variant:Nn \__tblr_build_hline:n { x, V }
+
+%% #1: row number, #2: column number
+\cs_new_protected:Npn \__tblr_build_hline_segment:nn #1 #2
+  {
+    \tl_set:Nx \l__tblr_n_tl
+      { \__tblr_prop_item:ne { hline } { [#1] / @hline-count } }
+    \tl_set:Nx \l__tblr_o_tl
+      { \__tblr_prop_item:ne { hline } { [#1][#2] / omit } }
+    \__tblr_get_col_outer_width_border_width:nNN {#2}
+      \l__tblr_col_o_wd_dim \l__tblr_col_b_wd_dim
+    \tl_if_empty:NTF \l__tblr_o_tl
+      {
+        \tl_if_empty:NF \l__tblr_n_tl
+          { \__tblr_build_hline_segment_real:nn {#1} {#2} }
+      }
+      { \__tblr_build_hline_segment_omit:nn {#1} {#2} }
+  }
+
+%% #1: row number, #2: column number
+\cs_new_protected:Npn \__tblr_build_hline_segment_omit:nn #1 #2
+  {
+    \skip_horizontal:n { \l__tblr_col_o_wd_dim - \l__tblr_col_b_wd_dim }
+  }
+
+%% #1: row number, #2: column number
+\cs_new_protected:Npn \__tblr_build_hline_segment_real:nn #1 #2
+  {
+    \tl_set:Nx \l__tblr_s_tl
+      { \__tblr_prop_item:ne { hline } { [#1] / rulesep } }
+    \vbox_set:Nn \l__tblr_c_box
+      {
+        %% add an empty hbox to support vbox width
+        \tex_hbox:D to \l__tblr_col_o_wd_dim {}
+        \int_step_inline:nn { \l__tblr_n_tl }
+          {
+            \tl_set:Nx \l__tblr_h_tl
+              { \__tblr_prop_item:ne { hline } { [#1](##1) / @hline-height } }
+            \hrule height ~ 0pt % remove lineskip
+            \hbox_set_to_wd:Nnn \l__tblr_b_box { \l__tblr_col_o_wd_dim }
+              {
+                \tl_set:Nx \l__tblr_f_tl
+                  { \__tblr_prop_item:ne { hline } { [#1][#2](##1) / fg } }
+                \tl_if_empty:NF \l__tblr_f_tl { \color{\l__tblr_f_tl} }
+                \__tblr_get_hline_segment_child:nnn {#1} {#2} {##1}
+              }
+            \box_set_ht:Nn \l__tblr_b_box { \l__tblr_h_tl }
+            \box_set_dp:Nn \l__tblr_b_box { 0pt }
+            \box_use:N \l__tblr_b_box
+            \skip_vertical:n { \l__tblr_s_tl }
+          }
+        \skip_vertical:n { - \l__tblr_s_tl }
+      }
+    \box_use:N \l__tblr_c_box
+    \skip_horizontal:n { - \l__tblr_col_b_wd_dim }
+  }
+
+%% Read from table specifications and calculate the widths of row and border
+%% column outer width = content width + colsep width + border width
+%% #1: the column number, #2: outer width, #3: border width
+\cs_new_protected:Npn \__tblr_get_col_outer_width_border_width:nNN #1 #2 #3
+  {
+    \dim_set:Nn #3
+      { \__tblr_prop_item:ne {vline} { [\int_eval:n {#1 + 1}] / @vline-width } }
+    \dim_set:Nn #2
+      {
+        \__tblr_prop_item:ne {vline} { [#1] / @vline-width }
+        +
+        \__tblr_prop_item:ne {column} { [#1] / leftsep }
+        +
+        \__tblr_prop_item:ne {column} { [#1] / @col-width }
+        +
+        \__tblr_prop_item:ne {column} { [#1] / rightsep }
+        +
+        #3
+      }
+  }
+
+\dim_new:N \l__tblr_row_ht_dim
+\dim_new:N \l__tblr_row_dp_dim
+\dim_new:N \l__tblr_row_abovesep_dim
+\dim_new:N \l__tblr_row_belowsep_dim
+
+%% Build current row, #1: row number
+\cs_new_protected:Npn \__tblr_build_row:N #1
+  {
+    \__tblr_get_row_inner_height_depth:VNNNN #1
+      \l__tblr_row_ht_dim \l__tblr_row_dp_dim
+      \l__tblr_row_abovesep_dim \l__tblr_row_belowsep_dim
+    \vrule width ~ 0pt ~ height ~ \l__tblr_row_ht_dim ~ depth ~ \l__tblr_row_dp_dim
+    \int_step_variable:nNn { \c at colcount } \l__tblr_j_tl
+      {
+        \__tblr_build_vline_segment:nn {#1} { \l__tblr_j_tl }
+        \__tblr_build_cell:NN #1 \l__tblr_j_tl
+      }
+    \__tblr_build_vline_segment:nn {#1} { \int_eval:n {\c at colcount + 1} }
+  }
+
+%% Read from table specifications and calculate inner height/depth of the row
+%% inner height = abovesep + above vspace + row upper
+%% inner depth = row lower + below vspace + belowsep
+%% #1: the row number; #2: resulting inner height; #3: resulting inner depth;
+%% #4: restulting abovesep; #5: restulting belowsep.
+
+\dim_new:N \l__row_upper_dim
+\dim_new:N \l__row_lower_dim
+\dim_new:N \l__row_vpace_dim
+
+\cs_new_protected:Npn \__tblr_get_row_inner_height_depth:nNNNN #1 #2 #3 #4 #5
+  {
+    \dim_set:Nn #4
+      { \__tblr_prop_item:ne { row } { [#1] / abovesep } }
+    \dim_set:Nn #5
+      { \__tblr_prop_item:ne { row } { [#1] / belowsep } }
+    \dim_set:Nn \l__row_upper_dim
+      {  \__tblr_prop_item:ne { row } { [#1] / @row-upper } }
+    \dim_set:Nn \l__row_lower_dim
+      {  \__tblr_prop_item:ne { row } { [#1] / @row-lower } }
+    \dim_set:Nn \l__row_vpace_dim
+      {
+        ( \__tblr_prop_item:ne { row } { [#1] / @row-height }
+          - \l__row_upper_dim - \l__row_lower_dim ) / 2
+      }
+    \dim_set:Nn #2 { #4 + \l__row_vpace_dim + \l__row_upper_dim }
+    \dim_set:Nn #3 { \l__row_lower_dim + \l__row_vpace_dim + #5 }
+  }
+\cs_generate_variant:Nn \__tblr_get_row_inner_height_depth:nNNNN { V }
+
+%% #1: row number, #2: column number
+\cs_new_protected:Npn \__tblr_build_vline_segment:nn #1 #2
+  {
+    \tl_set:Nx \l__tblr_n_tl
+      { \__tblr_prop_item:ne { vline } { [#2] / @vline-count } }
+    \tl_set:Nx \l__tblr_o_tl
+      { \__tblr_prop_item:ne { vline } { [#1][#2] / omit } }
+    \tl_if_empty:NTF \l__tblr_o_tl
+      {
+        \tl_if_empty:NF \l__tblr_n_tl
+          { \__tblr_build_vline_segment_real:nn {#1} {#2} }
+      }
+      { \__tblr_build_vline_segment_omit:nn {#1} {#2} }
+  }
+
+%% #1: row number, #2: column number
+\cs_new_protected:Npn \__tblr_build_vline_segment_omit:nn #1 #2
+  {
+    \tl_set:Nx \l__tblr_w_tl
+      { \__tblr_prop_item:ne { vline } { [#2] / @vline-width } }
+    \skip_horizontal:N \l__tblr_w_tl
+  }
+
+%% #1: row number, #2: column number
+%% We make every vline segment intersect with first hline below
+%% to remove gaps in vlines around multirow cells
+\cs_new_protected:Npn \__tblr_build_vline_segment_real:nn #1 #2
+  {
+    \tl_set:Nx \l__tblr_s_tl
+      { \__tblr_prop_item:ne { vline } { [#2] / rulesep } }
+    \tl_set:Nx \l__tblr_b_tl
+      {
+        \__tblr_prop_item:ne { hline }
+          { [\int_eval:n{#1 + 1}](1) / @hline-height }
+      }
+    \tl_if_empty:NT \l__tblr_b_tl { \tl_set:Nn \l__tblr_b_tl { 0pt } }
+    \hbox_set:Nn \l__tblr_a_box
+      {
+        \int_step_inline:nn { \l__tblr_n_tl }
+          {
+            \tl_set:Nx \l__tblr_w_tl
+              { \__tblr_prop_item:ne { vline } { [#2](##1) / @vline-width } }
+            \vbox_set_to_ht:Nnn \l__tblr_b_box
+              { \dim_eval:n { \l__tblr_row_ht_dim + \l__tblr_row_dp_dim } }
+              {
+                \tl_set:Nx \l__tblr_f_tl
+                  { \__tblr_prop_item:ne { vline } { [#1][#2](##1) / fg } }
+                \tl_if_empty:NF \l__tblr_f_tl { \color{\l__tblr_f_tl} }
+                \__tblr_get_vline_segment_child:nnnxx {#1} {#2} {##1}
+                  { \dim_eval:n { \l__tblr_row_ht_dim } }
+                  { \dim_eval:n { \l__tblr_row_dp_dim + \l__tblr_b_tl } }
+                \skip_vertical:n {  - \l__tblr_b_tl }
+              }
+            \box_set_wd:Nn \l__tblr_b_box { \l__tblr_w_tl }
+            \box_use:N \l__tblr_b_box
+            \skip_horizontal:n { \l__tblr_s_tl }
+          }
+        \skip_horizontal:n { - \l__tblr_s_tl }
+      }
+    \vbox_set:Nn \l__tblr_c_box { \box_use:N \l__tblr_a_box }
+    \box_set_ht:Nn \l__tblr_c_box { \dim_use:N \l__tblr_row_ht_dim }
+    \box_set_dp:Nn \l__tblr_c_box { \dim_use:N \l__tblr_row_dp_dim }
+    \box_use:N \l__tblr_c_box
+  }
+
+\tl_new:N \l__tblr_cell_rowspan_tl
+\tl_new:N \l__tblr_cell_colspan_tl
+\dim_new:N \l__tblr_cell_wd_dim
+\dim_new:N \l__tblr_cell_ht_dim
+
+\cs_new_protected:Npn \__tblr_build_cell:NN #1 #2
+  {
+    \int_set:Nn \c at rownum {#1}
+    \int_set:Nn \c at colnum {#2}
+    \group_begin:
+    \tl_set:Nx \l__tblr_w_tl
+      { \__tblr_prop_item:ne { column } { [#2] / @col-width } }
+    \tl_set:Nx \l__tblr_h_tl
+      { \__tblr_prop_item:ne { row } { [#1] / @row-height } }
+    \tl_set:Nx \l__tblr_x_tl
+      { \__tblr_prop_item:ne { column } { [#2] / leftsep} }
+    \tl_set:Nx \l__tblr_y_tl
+      { \__tblr_prop_item:ne { column } { [#2] / rightsep } }
+    \tl_set:Nx \l__tblr_cell_colspan_tl
+      { \__tblr_prop_item:ne { cell } { [#1][#2] / colspan } }
+    \tl_if_empty:NTF \l__tblr_cell_colspan_tl
+      { \dim_set:Nn \l__tblr_cell_wd_dim { \l__tblr_w_tl } }
+      {
+        \__tblr_get_span_horizontal_sizes:NNNNN #1 #2
+          \l__tblr_o_dim \l__tblr_cell_wd_dim \l__tblr_q_dim
+      }
+    \tl_set:Nx \l__tblr_cell_rowspan_tl
+      { \__tblr_prop_item:ne { cell } { [#1][#2] / rowspan } }
+    \tl_if_empty:NTF \l__tblr_cell_rowspan_tl
+      { \dim_set:Nn \l__tblr_cell_ht_dim { \l__tblr_h_tl } }
+      {
+        \__tblr_get_span_vertical_sizes:NNNNN #1 #2
+          \l__tblr_r_dim \l__tblr_cell_ht_dim \l__tblr_t_dim
+      }
+    \__tblr_get_cell_alignments:nn {#1} {#2}
+    \__tblr_build_cell_background:NN #1 #2
+    \__tblr_build_cell_content:NN #1 #2
+    \group_end:
+  }
+
+\cs_new_protected:Npn \__tblr_build_cell_content:NN #1 #2
+  {
+    \hbox_set_to_wd:Nnn \l__tblr_a_box { \l__tblr_cell_wd_dim }
+      {
+        \tl_if_eq:NnF \g__tblr_cell_halign_tl {l} { \hfil }
+        \__tblr_get_cell_text:nn {#1} {#2}
+        \tl_if_eq:NnF \g__tblr_cell_halign_tl {r} { \hfil }
+      }
+    \vbox_set_to_ht:Nnn \l__tblr_b_box { \l__tblr_cell_ht_dim }
+      {
+        \tl_case:Nn \g__tblr_cell_valign_tl
+          {
+            \c__tblr_valign_m_tl
+              {
+                \vfil
+                \tl_if_empty:NT \l__tblr_cell_rowspan_tl
+                  {
+                    \box_set_ht:Nn \l__tblr_a_box
+                      { \__tblr_prop_item:ne { row } { [#1] / @row-upper } }
+                    \box_set_dp:Nn \l__tblr_a_box
+                      { \__tblr_prop_item:ne { row } { [#1] / @row-lower } }
+                  }
+                \box_use:N \l__tblr_a_box
+                \vfil
+              }
+            \c__tblr_valign_h_tl
+              {
+                \box_set_ht:Nn \l__tblr_a_box
+                  { \__tblr_prop_item:ne { row } { [#1] / @row-head } }
+                \box_use:N \l__tblr_a_box
+                \vfil
+              }
+            \c__tblr_valign_f_tl
+              {
+                \vfil
+                \tl_if_empty:NTF \l__tblr_cell_rowspan_tl
+                  {
+                    \box_set_dp:Nn \l__tblr_a_box
+                      { \__tblr_prop_item:ne { row } { [#1] / @row-foot } }
+                  }
+                  {
+                    \box_set_dp:Nn \l__tblr_a_box
+                      {
+                        \__tblr_prop_item:ne { row }
+                          {
+                            [\int_eval:n {#1 + \l__tblr_cell_rowspan_tl - 1}]
+                            / @row-foot
+                          }
+                      }
+                  }
+                \box_use:N \l__tblr_a_box
+              }
+          }
+        \hrule height ~ 0pt %% zero depth
+      }
+    \vbox_set_to_ht:Nnn \l__tblr_c_box
+      { \l__tblr_row_ht_dim - \l__tblr_row_abovesep_dim }
+      {
+        \box_use:N \l__tblr_b_box
+        \vss
+      }
+    \skip_horizontal:n { \l__tblr_x_tl }
+    \box_use:N \l__tblr_c_box
+    \skip_horizontal:n { \l__tblr_y_tl - \l__tblr_cell_wd_dim + \l__tblr_w_tl }
+  }
+
+\cs_new_protected:Npn \__tblr_build_cell_background:NN #1 #2
+  {
+    \__tblr_prop_if_in:nxF {cell} { [#1][#2] / omit }
+      {
+        \group_begin:
+        \tl_set:Nx \l__tblr_b_tl
+          { \__tblr_prop_item:ne { cell } { [#1][#2] / background } }
+        \tl_if_empty:NF \l__tblr_b_tl
+          {
+            \__tblr_get_cell_background_width:NNN #1 #2 \l_tmpa_dim
+            \__tblr_get_cell_background_depth:NNN #1 #2 \l_tmpb_dim
+            \__tblr_build_cell_background:nnnn
+              { \dim_use:N \l_tmpa_dim }
+              { \l__tblr_row_ht_dim }
+              { \dim_use:N \l_tmpb_dim }
+              { \l__tblr_b_tl }
+          }
+        \group_end:
+      }
+  }
+
+%% #1: row number; #2: column number; #3 resulting dimension
+\cs_new_protected:Npn \__tblr_get_cell_background_width:NNN #1 #2 #3
+  {
+    \tl_if_empty:NTF \l__tblr_cell_colspan_tl
+      { \dim_set:Nn #3 { \l__tblr_x_tl + \l__tblr_w_tl + \l__tblr_y_tl } }
+      {
+        \dim_set:Nn #3 { \l__tblr_o_dim + \l__tblr_cell_wd_dim + \l__tblr_q_dim }
+      }
+  }
+
+%% #1: row number; #2: column number; #3 resulting dimension
+\cs_new_protected:Npn \__tblr_get_cell_background_depth:NNN #1 #2 #3
+  {
+    \tl_if_empty:NTF \l__tblr_cell_rowspan_tl
+      { \dim_set_eq:NN #3 \l__tblr_row_dp_dim }
+      {
+        \dim_set:Nn #3
+          {
+            \l__tblr_r_dim + \l__tblr_cell_ht_dim
+                           + \l__tblr_t_dim - \l__tblr_row_ht_dim
+          }
+      }
+  }
+
+%% #1: width, #2: height, #3: depth, #4: color
+\cs_new_protected:Npn \__tblr_build_cell_background:nnnn #1 #2 #3 #4
+  {
+    \hbox_set:Nn \l__tblr_a_box
+      {
+        \color {#4}
+        \vrule width ~ #1 ~ height ~ #2 ~ depth ~ #3
+      }
+    \box_set_dp:Nn \l__tblr_a_box { 0pt }
+    \box_use:N \l__tblr_a_box
+    \skip_horizontal:n { - #1 }
+  }
+
+%% #1: row number; #2: column number; #3: dimen register for rowsep above.
+%% #4: dimen register for total height; #5: dimen register for rowsep below.
+%% We can use \l__tblr_row_item_skip_size_prop which was made before
+\cs_new_protected:Npn \__tblr_get_span_vertical_sizes:NNNNN #1 #2 #3 #4 #5
+  {
+    \dim_set:Nn #3
+      { \__tblr_prop_item:ne { row } { [#1] / abovesep } }
+    \dim_zero:N #4
+    \int_step_inline:nnn { #1 } { #1 + \l__tblr_cell_rowspan_tl - 2 }
+      {
+        \dim_add:Nn #4
+          { \prop_item:Ne \l__tblr_row_item_skip_size_prop { itemskip[##1] } }
+      }
+    \dim_add:Nn #4
+      {
+        \prop_item:Ne \l__tblr_row_item_skip_size_prop
+          { item[\int_eval:n { #1 + \l__tblr_cell_rowspan_tl - 1 }] }
+      }
+    \dim_set:Nn #5
+      {
+        \__tblr_prop_item:ne { row }
+          { [\int_eval:n {#1 + \l__tblr_cell_rowspan_tl - 1}] / belowsep }
+      }
+    %\tl_log:x { cell[#1][#2] ~:~ \dim_use:N #3, \dim_use:N #4, \dim_use:N #5 }
+  }
+
+%% #1: row number; #2: column number; #3: dimen register for colsep left.
+%% #4: dimen register for total width; #5: dimen register for colsep right.
+%% We can use \l__tblr_col_item_skip_size_prop which was made before
+%% But when hspan=minimal, there are no itemskip in the prop list.
+%% Therefore we need to calculate them from the sizes of items and skips
+\cs_new_protected:Npn \__tblr_get_span_horizontal_sizes:NNNNN #1 #2 #3 #4 #5
+  {
+    \dim_set:Nn #3
+      { \__tblr_prop_item:ne { column } { [#2] / leftsep} }
+    \dim_zero:N #4
+    \int_step_inline:nnn { #2 } { #2 + \l__tblr_cell_colspan_tl - 2 }
+      {
+        \dim_add:Nn #4
+          { \prop_item:Ne \l__tblr_col_item_skip_size_prop { item[##1] } }
+        \dim_add:Nn #4
+          {
+            \prop_item:Ne \l__tblr_col_item_skip_size_prop
+              { skip[\int_eval:n { ##1 + 1 }] }
+          }
+      }
+    \dim_add:Nn #4
+      {
+        \prop_item:Ne \l__tblr_col_item_skip_size_prop
+          { item[\int_eval:n { #2 + \l__tblr_cell_colspan_tl - 1 }] }
+      }
+    \dim_set:Nn #5
+      {
+        \__tblr_prop_item:ne { column }
+          { [\int_eval:n {#2 + \l__tblr_cell_colspan_tl - 1}] / rightsep }
+      }
+    %\tl_log:x { cell[#1][#2] ~:~ \dim_use:N #3, \dim_use:N #4, \dim_use:N #5 }
+  }
+
+%%% --------------------------------------------------------
+%%  \section{Tracing Tabularray}
+%%% --------------------------------------------------------
+
+\NewDocumentCommand \SetTabularrayTracing { m }
+  {
+    \keys_set:nn { tblr-set-tracing } {#1}
+  }
+\cs_new_eq:NN \SetTblrTracing \SetTabularrayTracing
+
+\bool_new:N \g__tblr_tracing_text_bool
+\bool_new:N \g__tblr_tracing_command_bool
+\bool_new:N \g__tblr_tracing_table_bool
+\bool_new:N \g__tblr_tracing_column_bool
+\bool_new:N \g__tblr_tracing_row_bool
+\bool_new:N \g__tblr_tracing_cell_bool
+\bool_new:N \g__tblr_tracing_vline_bool
+\bool_new:N \g__tblr_tracing_hline_bool
+\bool_new:N \g__tblr_tracing_colspec_bool
+\bool_new:N \g__tblr_tracing_rowspec_bool
+\bool_new:N \g__tblr_tracing_target_bool
+\bool_new:N \g__tblr_tracing_cellspan_bool
+
+\keys_define:nn { tblr-set-tracing }
+  {
+    +text .code:n = \bool_gset_true:N \g__tblr_tracing_text_bool,
+    -text .code:n = \bool_gset_false:N \g__tblr_tracing_text_bool,
+    +command .code:n = \bool_gset_true:N \g__tblr_tracing_command_bool,
+    -command .code:n = \bool_gset_false:N \g__tblr_tracing_command_bool,
+    +table .code:n = \bool_gset_true:N \g__tblr_tracing_table_bool,
+    -table .code:n = \bool_gset_false:N \g__tblr_tracing_table_bool,
+    +column .code:n = \bool_gset_true:N \g__tblr_tracing_column_bool,
+    -column .code:n = \bool_gset_false:N \g__tblr_tracing_column_bool,
+    +row .code:n = \bool_gset_true:N \g__tblr_tracing_row_bool,
+    -row .code:n = \bool_gset_false:N \g__tblr_tracing_row_bool,
+    +cell .code:n = \bool_gset_true:N \g__tblr_tracing_cell_bool,
+    -cell .code:n = \bool_gset_false:N \g__tblr_tracing_cell_bool,
+    +vline .code:n = \bool_gset_true:N \g__tblr_tracing_vline_bool,
+    -vline .code:n = \bool_gset_false:N \g__tblr_tracing_vline_bool,
+    +hline .code:n = \bool_gset_true:N \g__tblr_tracing_hline_bool,
+    -hline .code:n = \bool_gset_false:N \g__tblr_tracing_hline_bool,
+    +colspec .code:n = \bool_gset_true:N \g__tblr_tracing_colspec_bool,
+    -colspec .code:n = \bool_gset_false:N \g__tblr_tracing_colspec_bool,
+    +rowspec .code:n = \bool_gset_true:N \g__tblr_tracing_rowspec_bool,
+    -rowspec .code:n = \bool_gset_false:N \g__tblr_tracing_rowspec_bool,
+    +target .code:n = \bool_gset_true:N \g__tblr_tracing_target_bool,
+    -target .code:n = \bool_gset_false:N \g__tblr_tracing_target_bool,
+    +cellspan .code:n = \bool_gset_true:N \g__tblr_tracing_cellspan_bool,
+    -cellspan .code:n = \bool_gset_false:N \g__tblr_tracing_cellspan_bool,
+    all .code:n = \__tblr_enable_all_tracings:,
+    none .code:n = \__tblr_disable_all_tracings:,
+  }
+
+\cs_new_protected_nopar:Npn \__tblr_enable_all_tracings:
+  {
+    \bool_gset_true:N \g__tblr_tracing_text_bool
+    \bool_gset_true:N \g__tblr_tracing_command_bool
+    \bool_gset_true:N \g__tblr_tracing_table_bool
+    \bool_gset_true:N \g__tblr_tracing_column_bool
+    \bool_gset_true:N \g__tblr_tracing_row_bool
+    \bool_gset_true:N \g__tblr_tracing_cell_bool
+    \bool_gset_true:N \g__tblr_tracing_vline_bool
+    \bool_gset_true:N \g__tblr_tracing_hline_bool
+    \bool_gset_true:N \g__tblr_tracing_colspec_bool
+    \bool_gset_true:N \g__tblr_tracing_rowspec_bool
+    \bool_gset_true:N \g__tblr_tracing_target_bool
+    \bool_gset_true:N \g__tblr_tracing_cellspan_bool
+  }
+
+\cs_new_protected_nopar:Npn \__tblr_disable_all_tracings:
+  {
+    \bool_gset_false:N \g__tblr_tracing_text_bool
+    \bool_gset_false:N \g__tblr_tracing_command_bool
+    \bool_gset_false:N \g__tblr_tracing_table_bool
+    \bool_gset_false:N \g__tblr_tracing_column_bool
+    \bool_gset_false:N \g__tblr_tracing_row_bool
+    \bool_gset_false:N \g__tblr_tracing_cell_bool
+    \bool_gset_false:N \g__tblr_tracing_vline_bool
+    \bool_gset_false:N \g__tblr_tracing_hline_bool
+    \bool_gset_false:N \g__tblr_tracing_colspec_bool
+    \bool_gset_false:N \g__tblr_tracing_rowspec_bool
+    \bool_gset_false:N \g__tblr_tracing_target_bool
+    \bool_gset_false:N \g__tblr_tracing_cellspan_bool
+  }
+
+\NewDocumentCommand \LogTabularrayTracing { m }
+  {
+    \keys_set:nn { tblr-log-tracing } {#1}
+  }
+\cs_new_eq:NN \LogTblrTracing \LogTabularrayTracing
+
+\keys_define:nn { tblr-log-tracing }
+  {
+    unknown .code:n = \__tblr_log_tracing:N \l_keys_key_str
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing:N #1
+  {
+    \bool_if:cT { g__tblr_tracing_ #1 _bool }
+      { \cs:w __tblr_log_tracing _ #1 : \cs_end: }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_text:
+  {
+    \__tblr_prop_log:n { text }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_command:
+  {
+    \__tblr_prop_log:n { command }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_table:
+  {
+    \__tblr_prop_log:n { table }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_column:
+  {
+    \__tblr_prop_log:n { column }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_row:
+  {
+    \__tblr_prop_log:n { row }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_cell:
+  {
+    \__tblr_prop_log:n { cell }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_vline:
+  {
+    \__tblr_prop_log:n { vline }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_hline:
+  {
+    \__tblr_prop_log:n { hline }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_colspec:
+  {
+    \tl_if_eq:NnT \g__tblr_column_or_row_tl { column }
+      { \tl_log:N \g__tblr_expanded_colrow_spec_tl }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_rowspec:
+  {
+    \tl_if_eq:NnT \g__tblr_column_or_row_tl { row }
+      { \tl_log:N \g__tblr_expanded_colrow_spec_tl }
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_target:
+  {
+    \dim_log:N \l__column_target_dim
+    \prop_log:N \l__column_coefficient_prop
+    \prop_log:N \l__column_natural_width_prop
+    \prop_log:N \l__column_computed_width_prop
+  }
+
+\cs_new_protected:Npn \__tblr_log_tracing_cellspan:
+  {
+    \prop_log:N \l__tblr_col_item_skip_size_prop
+    \prop_log:N \l__tblr_col_span_size_prop
+    \prop_log:N \l__tblr_row_item_skip_size_prop
+    \prop_log:N \l__tblr_row_span_size_prop
+    \prop_log:N \l__tblr_row_span_to_row_prop
+  }
+
+\cs_new_protected:Npn \__tblr_do_if_tracing:nn #1 #2
+  {
+    \bool_if:cT { g__tblr_tracing_ #1 _bool } {#2}
+  }
+
+\ExplSyntaxOff
+


Property changes on: trunk/Master/texmf-dist/tex/latex/tabularray/tabularray.sty
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/tlpkg/bin/tlpkg-ctan-check
===================================================================
--- trunk/Master/tlpkg/bin/tlpkg-ctan-check	2021-05-13 21:13:13 UTC (rev 59182)
+++ trunk/Master/tlpkg/bin/tlpkg-ctan-check	2021-05-13 21:14:08 UTC (rev 59183)
@@ -732,7 +732,7 @@
     tabfigures table-fct tableaux tablefootnote tableof tablestyles
     tablists tablor tabls tablvar
     tabriz-thesis tabstackengine tabto-generic tabto-ltx
-    tabu tabularborder tabularcalc tabularew
+    tabu tabularborder tabularray tabularcalc tabularew
     tabulary tabvar tagging tagpair tagpdf talk tamefloats
     tamethebeast tap tapir tasks tcldoc tcolorbox tdclock tdsfrmath
     technics technion-thesis-template ted

Modified: trunk/Master/tlpkg/tlpsrc/collection-latexextra.tlpsrc
===================================================================
--- trunk/Master/tlpkg/tlpsrc/collection-latexextra.tlpsrc	2021-05-13 21:13:13 UTC (rev 59182)
+++ trunk/Master/tlpkg/tlpsrc/collection-latexextra.tlpsrc	2021-05-13 21:14:08 UTC (rev 59183)
@@ -1191,6 +1191,7 @@
 depend tabto-ltx
 depend tabu
 depend tabularborder
+depend tabularray
 depend tabularcalc
 depend tabularew
 depend tabulary

Added: trunk/Master/tlpkg/tlpsrc/tabularray.tlpsrc
===================================================================


More information about the tex-live-commits mailing list.