%%% % -*- coding: utf-8 -*-
%%% ----------------------------------------------------------------------------
%%% Caption     : Typeset tabulars and arrays with LaTeX3
%%% Author      : Jianrui Lyu <tolvjr@163.com>
%%% Collaborator: Yukai Chou <muzimuzhi@gmail.com>
%%% CTAN Page   : https://ctan.org/pkg/tabularray
%%% Repository  : https://github.com/lvjr/tabularray
%%% License     : The LaTeX Project Public License 1.3c
%%% ----------------------------------------------------------------------------

%%% --------------------------------------------------------
%%> \section{Scratch variables and function variants}
%%% --------------------------------------------------------

%% \DeclareRelease and \DeclareCurrentRelease are added in LaTeX 2018-04-01
\NeedsTeXFormat{LaTeX2e}[2018-04-01]
\providecommand\DeclareRelease[3]{}
\providecommand\DeclareCurrentRelease[2]{}
\DeclareRelease{v2024}{2022-01-01}{tabularray-2024.sty}
\DeclareRelease{v2021}{2021-01-01}{tabularray-2021.sty}
\DeclareCurrentRelease{}{2025-01-01}

\ProvidesExplPackage{tabularray}{2025-03-11}{2025A}
  {Typeset tabulars and arrays with LaTeX3}

%% \IfFormatAtLeastTF, xparse and lthooks are added in LaTeX 2020-10-01
%% Note that \@ifl@t@r or \@ifpackagelater means 'this date or later'
\msg_new:nnn { tabularray } { latex-too-old }
  {
    Your ~ LaTeX ~ release ~ is ~ too ~ old. \\
    Please ~ update ~ it ~ to ~ 2022-11-01 ~ first.
  }
\@ifl@t@r\fmtversion{2022-11-01}{}{
  \msg_error:nn { tabularray } { latex-too-old }
}

\AddToHook {package/xcolor/after} [tabularray] { \RequirePackage{ninecolors} }

\newenvironment{tblrNoHyper}{}{}
\AddToHook {package/hyperref/after} [tabularray]
  { \renewenvironment{tblrNoHyper}{\NoHyper}{\endNoHyper} }

\NewDocumentCommand \TblrParboxRestore { } { \@parboxrestore }

\NewDocumentCommand \TblrAlignBoth { }
  {
    \let \\ = \@normalcr
    \leftskip = \z@skip
    \@rightskip = \z@skip
    \rightskip = \@rightskip
    \parfillskip = \@flushglue
  }

\NewDocumentCommand \TblrAlignLeft { } { \raggedright }

\NewDocumentCommand \TblrAlignCenter { } { \centering }

\NewDocumentCommand \TblrAlignRight { } { \raggedleft }

\cs_set_eq:NN \TblrNewPage \newpage

%% Note that \cs_if_exist:NTF doesn't treat \relax as an existing command.
%% Therefore we define our \__tblr_cs_if_defined:NTF here.
\prg_set_conditional:Npnn \__tblr_cs_if_defined:N #1 { p, T, F, TF }
  {
    %% \if_cs_exist:N = \ifdefined in eTeX
    \if_cs_exist:N #1
      \prg_return_true:
    \else:
      \prg_return_false:
    \fi:
  }
\prg_set_conditional:Npnn \__tblr_cs_if_defined:c #1 { p, T, F, TF }
  {
    %% \if_cs_exist:w = \ifcsname in eTeX
    \if_cs_exist:w #1 \cs_end:
      \prg_return_true:
    \else:
      \prg_return_false:
    \fi:
  }

\cs_generate_variant:Nn \msg_error:nnnnn { nnnVV }
\cs_generate_variant:Nn \seq_map_indexed_inline:Nn { cn }
\cs_generate_variant:Nn \seq_set_split:Nnn { NVe }
\cs_generate_variant:Nn \seq_set_split_keep_spaces:Nnn { Ne }
\cs_generate_variant:Nn \seq_use:Nn { Ne }
\cs_generate_variant:Nn \tl_gput_right:Nn { Nf }
\cs_generate_variant:Nn \tl_rescan:nn { ne }
\prg_generate_conditional_variant:Nnn \clist_if_in:Nn { Ne } { TF }
\prg_generate_conditional_variant:Nnn \str_if_eq:nn { en } { TF }
\prg_generate_conditional_variant:Nnn \str_if_in:Nn { Ne } { TF }

%% Add missing function variants for texlive 2022. They can be removed in 2026.
\cs_generate_variant:Nn \clist_gput_right:Nn { ce }
\cs_generate_variant:Nn \clist_put_right:Nn { ce }
\cs_generate_variant:Nn \clist_set:Nn { Ne, ce }
\cs_generate_variant:Nn \keyval_parse:NNn { NNV }
\cs_generate_variant:Nn \msg_error:nnn { nnV }
\cs_generate_variant:Nn \msg_error:nnnn { nnV }
\cs_generate_variant:Nn \prop_item:Nn { Ne, NV }
\cs_generate_variant:Nn \prop_put:Nnn { Nne, Nen, Nee, NeV }
\cs_generate_variant:Nn \seq_put_right:Nn { Ne }
\cs_generate_variant:Nn \seq_set_split:Nnn { Nne, NVn }
\cs_generate_variant:Nn \str_gset:Nn { Ne }
\cs_generate_variant:Nn \tl_const:Nn { ce }
\cs_generate_variant:Nn \tl_gput_right:Nn { Ne }
\cs_generate_variant:Nn \tl_gset:Nn { Ne, ce }
\cs_generate_variant:Nn \tl_log:n { e }
\cs_generate_variant:Nn \tl_put_left:Nn { Ne, Nv }
\cs_generate_variant:Nn \tl_put_right:Nn { Ne }
\cs_generate_variant:Nn \tl_set:Nn { Ne, ce }
\cs_generate_variant:Nn \tl_set_rescan:Nnn { Nne, NnV }
\cs_generate_variant:Nn \tl_to_str:n { e }
\prg_generate_conditional_variant:Nnn \prop_if_in:Nn { c } { T }
\prg_generate_conditional_variant:Nnn \tl_if_eq:nn { en } { T, TF }
\prg_generate_conditional_variant:Nnn \tl_if_in:nn { nV } { TF }
\prg_generate_conditional_variant:Nnn \tl_if_in:Nn { Ne } { TF }
\prg_generate_conditional_variant:Nnn \tl_if_head_eq_catcode:nN { VN } { 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

%% Some commands for horizontal alignment
\cs_new_eq:NN \__tblr_halign_command_j: \TblrAlignBoth
\cs_new_eq:NN \__tblr_halign_command_l: \TblrAlignLeft
\cs_new_eq:NN \__tblr_halign_command_c: \TblrAlignCenter
\cs_new_eq:NN \__tblr_halign_command_r: \TblrAlignRight

%% Total number of tblr tables, used for creating hyperref targets and tikz nodes.
%% We need to save and restore it before and after measuring stage respectively,
%% so we must define it with \newcounter command.
\newcounter { tblrcount }

%% Some counters for row and column numbering.
%% We may need to restore all LaTeX counters in measuring and building cells,
%% so we must not define these counters with \newcounter command.
\int_zero_new:N \c@rownum
\int_zero_new:N \c@colnum
\int_zero_new:N \c@rowcount
\int_zero_new:N \c@colcount

%% Add missing \therownum, \thecolnum, \therowcount, \thecolcount (issue #129)
\ProvideExpandableDocumentCommand \therownum {} { \@arabic \c@rownum }
\ProvideExpandableDocumentCommand \thecolnum {} { \@arabic \c@colnum }
\ProvideExpandableDocumentCommand \therowcount {} { \@arabic \c@rowcount }
\ProvideExpandableDocumentCommand \thecolcount {} { \@arabic \c@colcount }

%% Some dimensions for row and column spacing
\dim_new:N \abovesep
\dim_new:N \belowsep
\dim_new:N \leftsep
\dim_new:N \rightsep

%% Some functions for lwarp to remove rules and boxes
\cs_new:Npn \tblr_hrule_ht:n #1
  {
    \hrule height ~ #1 \scan_stop:
  }
\cs_new:Npn \tblr_vrule_wd_ht_dp:nnn #1 #2 #3
  {
    \vrule width ~ #1 ~ height ~ #2 ~ depth ~ #3 \scan_stop:
  }
\cs_new_protected:Npn \tblr_box_use:N #1
  {
    \box_use:N #1
  }
\cs_new_protected:Npn \tblr_vbox_set:Nn #1 #2
  {
    \vbox_set:Nn #1 {#2}
  }

%%% --------------------------------------------------------
%%> \section{Functions for splitting, extracting and matching}
%%% --------------------------------------------------------

\str_const:Nn \c__tblr_left_bracket_str { [ }

\cs_new_protected:Npn \__tblr_split_before_brace:NNn #1 #2 #3
  {
    \__tblr_split_before:NNVn #1 #2 \c_left_brace_str {#3}
  }

\cs_new_protected:Npn \__tblr_split_before_bracket:NNn #1 #2 #3
  {
    \__tblr_split_before:NNVn #1 #2 \c__tblr_left_bracket_str {#3}
  }
\cs_generate_variant:Nn \__tblr_split_before_bracket:NNn { NNV }

%% Split tl #2 as two parts: sub tl before first #3 and sub tl from first #1.
%% And the results are stored in tl vars #1 and #2, respectively.
\cs_new_protected:Npn \__tblr_split_before:NNnn #1 #2 #3 #4
  {
    \cs_set_protected:Npn \__tblr_split_before_auxa:ww ##1 #3 ##2 \q_stop
      {
        \tl_set:Nn #1 { ##1 }
        \tl_if_empty:nTF { ##2 }
          { \tl_set:Nn #2 { ##2 } } { \__tblr_split_before_auxb:w ##2 \q_stop }
      }
    \cs_set_protected:Npn \__tblr_split_before_auxb:w ##1 #3 \q_stop
      {
        \tl_set:Nn #2 { #3 ##1 }
      }
    \__tblr_split_before_auxa:ww #4 #3 \q_stop
  }
\cs_generate_variant:Nn \__tblr_split_before:NNnn { NNV }

%% The name of a child indexer must start with uppercase letter.
%% The name of a child selector must start with lowercase letter.

\tl_const:Nn \c__tblr_upper_letter_tl { ABCDEFGHIJKLMNOPQRSTUVWXYZ }
\tl_const:Nn \c__tblr_lower_letter_tl { abcdefghijklmnopqrstuvwxyz }
\tl_const:Nn \c__tblr_digit_str { 0123456789 }

\prg_new_protected_conditional:Npnn \__tblr_if_head_upper:n #1 { TF }
  {
    \tl_if_in:NeTF \c__tblr_upper_letter_tl { \tl_head:n { #1 } }
      { \prg_return_true: } { \prg_return_false: }
  }

\prg_new_protected_conditional:Npnn \__tblr_if_head_lower:n #1 { TF }
  {
    \tl_if_in:NeTF \c__tblr_lower_letter_tl { \tl_head:n { #1 } }
      { \prg_return_true: } { \prg_return_false: }
  }

\prg_new_protected_conditional:Npnn \__tblr_tl_if_upper:n #1 { TF }
  {
    \tl_if_in:NnTF \c__tblr_upper_letter_tl { #1 }
      { \prg_return_true: } { \prg_return_false: }
  }
\prg_generate_conditional_variant:Nnn \__tblr_tl_if_upper:n { V } { TF }

\prg_new_protected_conditional:Npnn \__tblr_tl_if_lower:n #1 { TF }
  {
    \tl_if_in:NnTF \c__tblr_lower_letter_tl { #1 }
      { \prg_return_true: } { \prg_return_false: }
  }
\prg_generate_conditional_variant:Nnn \__tblr_tl_if_lower:n { V } { TF }

\prg_new_protected_conditional:Npnn \__tblr_tl_if_digit:n #1 { F, TF }
  {
    \tl_if_in:NnTF \c__tblr_digit_tl { #1 }
      { \prg_return_true: } { \prg_return_false: }
  }
\prg_generate_conditional_variant:Nnn \__tblr_tl_if_digit:n { e, V } { F, TF }

%% When the key name is omitted, we need to detect the name from its value.

\str_const:Nn \c__tblr_letter_str
  { ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz }

%% It is enough to check only the first item
\prg_new_protected_conditional:Npnn \__tblr_if_color_value:n #1 { T, F, TF }
  {
    \str_if_in:NeTF \c__tblr_letter_str { \tl_head:n { #1 } }
      { \prg_return_true: } { \prg_return_false: }
  }

\str_const:Nn \c__tblr_number_str { 0123456789+-.~ }
\bool_new:N \l__tblr_if_number_bool

%% The number value could be '25.6 - 3.14'.
\prg_new_protected_conditional:Npnn \__tblr_if_number_value:n #1 { T, F, TF }
  {
    \bool_set_true:N \l__tblr_if_number_bool
    \tl_map_inline:nn { #1 }
      {
        \str_if_in:NnF \c__tblr_number_str { ##1 }
          {
            \bool_set_false:N \l__tblr_if_number_bool
            \tl_map_break:
          }
      }
    \bool_if:NTF \l__tblr_if_number_bool
      { \prg_return_true: } { \prg_return_false: }
  }

\tl_const:Nn \c__tblr_digit_tl { 0123456789 }

%% It is enough to check only the first item
\prg_new_protected_conditional:Npnn \__tblr_if_positive_value:n #1 { T, F, TF }
  {
    \tl_if_in:NeTF \c__tblr_digit_tl { \tl_head:n { #1 } }
      { \prg_return_true: } { \prg_return_false: }
  }
\prg_generate_conditional_variant:Nnn \__tblr_if_positive_value:n { V } { TF }

%% It is enough to check only the first item
\prg_new_protected_conditional:Npnn \__tblr_if_negative_value:n #1 { T, F, TF }
  {
    \tl_if_eq:enTF { \tl_head:n { #1 } } { - }
      { \prg_return_true: } { \prg_return_false: }
  }
\prg_generate_conditional_variant:Nnn \__tblr_if_negative_value:n { V } { TF }

%%% --------------------------------------------------------
%%> \section{Declare and set tabularray keys}
%%% --------------------------------------------------------

\cs_new_protected:Npn \__tblr_keys_define:nn #1 #2
  {
    \keys_define:nn { tabularray/#1 } {#2}
  }

\cs_set_eq:NN \DeclareTblrKeys \__tblr_keys_define:nn

\cs_new_protected:Npn \__tblr_keys_set:nn #1 #2
  {
    \keys_set:nn { tabularray/#1 } {#2}
  }
\cs_generate_variant:Nn \__tblr_keys_set:nn { nV, nv, ne }

\cs_set_eq:NN \SetTblrKeys \__tblr_keys_set:nn

\cs_new_protected:Npn \__tblr_keys_set_groups:nnn #1 #2 #3
  {
    \keys_set_groups:nnn { tabularray/#1 } {#2} {#3}
  }

\prg_set_conditional:Npnn \__tblr_keys_if_exist:nn #1 #2 { p, T, F, TF }
  {
    \keys_if_exist:nnTF { tabularray/#1 } { #2 }
      { \prg_return_true: } { \prg_return_false: }
  }
\prg_generate_conditional_variant:Nnn \__tblr_keys_if_exist:nn { nV } { TF }

%% A special key such as cell{i}{j} or note{a} consists of name and args.
%% These functions extracts two components of a special key as string #1.

\seq_new:N \l__tblr_key_split_seq
\str_new:N \l__tblr_key_split_name_str
\tl_new:N \l__tblr_key_split_args_tl

\cs_new_protected:Npn \__tblr_key_split_name:n #1
  {
    \seq_set_split_keep_spaces:Nen \l__tblr_key_split_seq \c_left_brace_str {#1}
    \seq_pop_left:NN \l__tblr_key_split_seq \l__tblr_key_split_name_str
    %\str_log:N \l__tblr_key_split_name_str
  }
\cs_generate_variant:Nn \__tblr_key_split_name:n { e }

\cs_new_protected:Npn \__tblr_key_split_name_args:n #1
  {
    \__tblr_key_split_name:n {#1}
    \seq_if_empty:NTF \l__tblr_key_split_seq
      { \tl_clear:N \l__tblr_key_split_args_tl }
      {
        \tl_set_rescan:Nne \l__tblr_key_split_args_tl {}
          {
            \c_left_brace_str
            \seq_use:Ne \l__tblr_key_split_seq { \c_left_brace_str }
          }
      }
  }

%% To distinguish between modern table specs and traditional column specs,
%% we need to extract first key name from tl #1 which is an argument of tblr.

\cs_new_protected:Npn \__tblr_keyval_extract_first_name:n #1
  {
    \seq_set_split:Nnn \l__tblr_key_split_seq {,} {#1}
    \seq_set_split:Nne \l__tblr_key_split_seq {=}
      { \seq_item:Nn \l__tblr_key_split_seq {1} }
    \__tblr_key_split_name:e
      { \tl_to_str:e { \seq_item:Nn \l__tblr_key_split_seq {1} } }
  }

%%% --------------------------------------------------------
%%> \section{Create and use tabularray hooks}
%%% --------------------------------------------------------

\cs_new_protected:Npn \__tblr_hook_new:n #1
  {
    \hook_new:n { tabularray/#1 }
  }

\cs_new_protected:Npn \__tblr_hook_new_pair:nn #1 #2
  {
    \hook_new_pair:nn { tabularray/#1 } { tabularray/#2 }
  }

\cs_new_protected:Npn \__tblr_hook_use_false:n #1 {}

\cs_new_protected:Npn \__tblr_hook_use_true:n #1
  {
    \hook_use:n { tabularray/#1 }
  }

\cs_set_eq:NN \__tblr_hook_use:n \__tblr_hook_use_false:n

\NewDocumentCommand \AddToTblrHook { m o +m }
  {
    \hook_gput_code:nnn  { tabularray/#1 } { #2 } { #3 }
  }

\NewDocumentCommand \AddToTblrHookNext { m +m }
  {
    \hook_gput_next_code:nn  { tabularray/#1 } { #2 }
  }

%%% --------------------------------------------------------
%%> \section{Data structures based on property lists}
%%% --------------------------------------------------------

\int_new:N \gTblrLevelInt % store table nesting level

%% \g_tblr_level_int is deprecated and will be removed later
\cs_set_eq:NN \g_tblr_level_int \gTblrLevelInt

\cs_new_protected:Npn \__tblr_clear_prop_lists:
  {
    \__tblr_prop_gclear_new:c
      { g__tblr_text_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_command_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_inner_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_outer_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_note_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_remark_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_more_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_row_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_column_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_cell_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_hline_ \int_use:N \gTblrLevelInt _prop }
    \__tblr_prop_gclear_new:c
      { g__tblr_vline_ \int_use:N \gTblrLevelInt _prop }
  }

\cs_new_protected:Npn \__tblr_prop_gput:nnn #1 #2 #3
  {
    \prop_gput:cnn
      { g__tblr_#1_ \int_use:N \gTblrLevelInt _prop } { #2 } { #3 }
  }
\cs_generate_variant:Nn \__tblr_prop_gput:nnn { nne, nnV, nen, nee, neV }

\cs_new:Npn \__tblr_prop_item:nn #1 #2
  {
    \prop_item:cn { g__tblr_#1_ \int_use:N \gTblrLevelInt _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 \gTblrLevelInt _prop }
  }
\cs_new_protected:Npn \__tblr_prop_if_in:nnF #1
  {
    \prop_if_in:cnF { g__tblr_#1_ \int_use:N \gTblrLevelInt _prop }
  }
\cs_new_protected:Npn \__tblr_prop_if_in:nnTF #1
  {
    \prop_if_in:cnTF { g__tblr_#1_ \int_use:N \gTblrLevelInt _prop }
  }
\prg_generate_conditional_variant:Nnn \__tblr_prop_if_in:nn { ne } { T, F, TF }

\cs_new_protected:Npn \__tblr_prop_log:n #1
  {
    \prop_log:c { g__tblr_#1_ \int_use:N \gTblrLevelInt _prop }
  }

\cs_new_protected:Npn \__tblr_prop_map_inline:nn #1 #2
  {
    \prop_map_inline:cn { g__tblr_#1_ \int_use:N \gTblrLevelInt _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 \gTblrLevelInt _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 \gTblrLevelInt _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:Ne \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 { Nee, NnV }

\cs_new_protected:Npn \__tblr_gput_if_larger:Nnn #1 #2 #3
  {
    \tl_set:Ne \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 { 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:Nne #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:Nne #1 { #2 } { \dim_eval:n { \prop_item:Nn #1 { #2 } + #3 } }
  }
\cs_generate_variant:Nn \__tblr_gadd_dimen_value:Nnn { cnn }

%%% --------------------------------------------------------
%%> \section{Data structures based on token lists}
%%% --------------------------------------------------------

\cs_new_protected:Npn \__tblr_clear_spec_lists:
  {
    %\__tblr_clear_one_spec_lists:n { row }
    %\__tblr_clear_one_spec_lists:n { column }
    %\__tblr_clear_one_spec_lists:n { cell }
    \__tblr_clear_one_spec_lists:n { text }
    \__tblr_clear_one_spec_lists:n { hline }
    \__tblr_clear_one_spec_lists:n { vline }
    \__tblr_clear_one_spec_lists:n { outer }
  }

\cs_new_protected:Npn \__tblr_clear_one_spec_lists:n #1
  {
    \clist_if_exist:cTF { g__tblr_#1_ \int_use:N \gTblrLevelInt _clist }
      {
        \clist_map_inline:cn { g__tblr_#1_ \int_use:N \gTblrLevelInt _clist }
          {
            \tl_gclear_new:c { g__tblr_spec_ \int_use:N \gTblrLevelInt _#1_##1_tl }
          }
      }
      { \clist_new:c { g__tblr_#1_ \int_use:N \gTblrLevelInt _clist } }
  }

\cs_new_protected:Npn \__tblr_spec_gput:nnn #1 #2 #3
  {
    \tl_if_exist:cF { g__tblr_spec_ \int_use:N \gTblrLevelInt _#1_#2_tl }
      { \tl_new:c { g__tblr_spec_ \int_use:N \gTblrLevelInt _#1_#2_tl } }
    \tl_gset:cn
      { g__tblr_spec_ \int_use:N \gTblrLevelInt _#1_#2_tl } {#3}
    \clist_gput_right:ce { g__tblr_#1_ \int_use:N \gTblrLevelInt _clist } {#2}
  }
\cs_generate_variant:Nn \__tblr_spec_gput:nnn { nne, nnV, nen, nee, neV }

\cs_new:Npn \__tblr_spec_item:nn #1 #2
  {
    \tl_if_exist:cT { g__tblr_spec_ \int_use:N \gTblrLevelInt _#1_#2_tl }
      {
        \exp_args:Nv \exp_not:n
          { g__tblr_spec_ \int_use:N \gTblrLevelInt _#1_#2_tl }
      }
  }
\cs_generate_variant:Nn \__tblr_spec_item:nn { ne }

\cs_new_protected:Npn \__tblr_spec_gput_if_larger:nnn #1 #2 #3
  {
    \tl_set:Ne \l__tblr_put_if_larger_tl { \__tblr_spec_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 } }
      { \__tblr_spec_gput:nnn {#1} {#2} {#3} }
  }
\cs_generate_variant:Nn \__tblr_spec_gput_if_larger:nnn { nne, nnV, nen, nee, neV }

\cs_new_protected:Npn \__tblr_spec_gadd_dimen_value:nnn #1 #2 #3
  {
    \__tblr_spec_gput:nne {#1} {#2}
      { \dim_eval:n { \__tblr_spec_item:ne {#1} {#2} + #3 } }
  }
\cs_generate_variant:Nn \__tblr_spec_gadd_dimen_value:nnn { nne, nnV, nen, nee }

\cs_new_protected:Npn \__tblr_spec_log:n #1
  {
    \clist_gremove_duplicates:c
      { g__tblr_#1_ \int_use:N \gTblrLevelInt _clist }
    \tl_log:e
      {
        The ~ spec ~ list ~ #1 _ \int_use:N \gTblrLevelInt
              \space contains ~ the ~ pairs:
      }
    \clist_map_inline:cn { g__tblr_#1_ \int_use:N \gTblrLevelInt _clist }
      {
        \tl_log:e
          {
            \space { ##1 } ~\space=>~\space { \__tblr_spec_item:nn {#1} {##1} }
          }
      }
  }

%%% --------------------------------------------------------
%%> \section{Data structures based on integer arrays}
%%% --------------------------------------------------------

\msg_new:nnn { tabularray } { intarray-beyond-bound }
  { Position ~ #2 ~ is ~ beyond ~ the ~ bound ~ of ~ intarray ~ #1.}

\cs_new_protected:Npn \__tblr_intarray_gset:Nnn #1 #2 #3
  {
    \bool_lazy_or:nnTF
      { \int_compare_p:nNn {#2} < {0} }
      { \int_compare_p:nNn {#2} > {\intarray_count:N #1} }
      {
        \bool_if:NT \g__tblr_tracing_intarray_bool
          { \msg_warning:nnnn { tabularray } { intarray-beyond-bound } {#1} {#2} }
      }
      { \intarray_gset:Nnn #1 {#2} {#3} }
  }
\cs_generate_variant:Nn \__tblr_intarray_gset:Nnn { cnn }

%% #1: data name; #2: key name; #3: value type
\cs_new_protected:Npn \__tblr_data_new_key:nnn #1 #2 #3
  {
    \int_gincr:c { g__tblr_data_#1_key_count_int }
    \tl_const:ce
      {
        c__tblr_data_#1_key_name_
          \int_use:c { g__tblr_data_#1_key_count_int } _tl
      }
      { #2 }
    \tl_const:ce { c__tblr_data_#1_key_number_#2_tl }
      { \int_use:c { g__tblr_data_#1_key_count_int } }
    \tl_const:cn { c__tblr_data_#1_key_type_#2_tl } {#3}
  }

\int_new:N \g__tblr_data_row_key_count_int
\__tblr_data_new_key:nnn { row } { height }      { dim }
\__tblr_data_new_key:nnn { row } { coefficient } { dec }
\__tblr_data_new_key:nnn { row } { abovesep }    { dim }
\__tblr_data_new_key:nnn { row } { belowsep }    { dim }
\__tblr_data_new_key:nnn { row } { @row-height } { dim }
\__tblr_data_new_key:nnn { row } { @row-head }   { dim }
\__tblr_data_new_key:nnn { row } { @row-foot }   { dim }
\__tblr_data_new_key:nnn { row } { @row-upper }  { dim }
\__tblr_data_new_key:nnn { row } { @row-lower }  { dim }

\int_new:N \g__tblr_data_column_key_count_int
\__tblr_data_new_key:nnn { column } { width }       { dim }
\__tblr_data_new_key:nnn { column } { coefficient } { dec }
\__tblr_data_new_key:nnn { column } { leftsep }     { dim }
\__tblr_data_new_key:nnn { column } { rightsep }    { dim }
\__tblr_data_new_key:nnn { column } { @col-width }  { dim }

\int_new:N \g__tblr_data_cell_key_count_int
\__tblr_data_new_key:nnn { cell } { width }        { dim }
\__tblr_data_new_key:nnn { cell } { rowspan }      { int }
\__tblr_data_new_key:nnn { cell } { colspan }      { int }
\__tblr_data_new_key:nnn { cell } { halign }       { str }
\__tblr_data_new_key:nnn { cell } { valign }       { str }
\__tblr_data_new_key:nnn { cell } { background }   { str }
\__tblr_data_new_key:nnn { cell } { foreground }   { str }
\__tblr_data_new_key:nnn { cell } { font }         { str }
\__tblr_data_new_key:nnn { cell } { mode }         { str }
\__tblr_data_new_key:nnn { cell } { cmd }          { str }
\__tblr_data_new_key:nnn { cell } { omit }         { int }
\__tblr_data_new_key:nnn { cell } { @cell-width }  { dim }
\__tblr_data_new_key:nnn { cell } { @cell-height } { dim }
\__tblr_data_new_key:nnn { cell } { @cell-depth }  { dim }

\clist_const:Nn \c__tblr_data_clist { row, column, cell }
\tl_const:Nn \c__tblr_data_row_count_tl { \c@rowcount }
\tl_const:Nn \c__tblr_data_column_count_tl { \c@colcount }
\tl_const:Nn \c__tblr_data_cell_count_tl { \c@rowcount * \c@colcount }
\tl_const:Nn \c__tblr_data_row_index_number_tl {1}
\tl_const:Nn \c__tblr_data_column_index_number_tl {1}
\tl_const:Nn \c__tblr_data_cell_index_number_tl {2}
\int_new:N \g__tblr_array_int

\cs_new_protected:Npn \__tblr_init_table_data:
  {
    \clist_map_function:NN \c__tblr_data_clist \__tblr_init_one_data:n
  }

\cs_new_protected:Npn \__tblr_init_one_data:n #1
  {
    \int_gincr:N \g__tblr_array_int
    \intarray_new:cn { g__tblr_#1_ \int_use:N \g__tblr_array_int _intarray }
      {
        \int_use:c { g__tblr_data_#1_key_count_int }
          * \tl_use:c { c__tblr_data_#1_count_tl }
      }
    \cs_set_eq:cc { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
      { g__tblr_#1_ \int_use:N \g__tblr_array_int _intarray }
    %\intarray_log:c { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
  }

%% #1: data name; #2: data index; #3: key name
\cs_new:Npn \__tblr_data_key_to_int:nnn #1 #2 #3
  {
    ( #2 - 1 ) * \int_use:c { g__tblr_data_#1_key_count_int }
      + \tl_use:c { c__tblr_data_#1_key_number_#3_tl }
  }

%% #1: data name; #2: data index 1; #3: data index 2; #4: key name
\cs_new:Npn \__tblr_data_key_to_int:nnnn #1 #2 #3 #4
  {
    ( #2 - 1 ) * \c@colcount * \int_use:c { g__tblr_data_#1_key_count_int }
      + ( #3 - 1 ) * \int_use:c { g__tblr_data_#1_key_count_int }
      + \tl_use:c { c__tblr_data_#1_key_number_#4_tl }
  }

\int_new:N \l__tblr_key_count_int
\int_new:N \l__tblr_key_quotient_int
\int_new:N \l__tblr_key_quotient_two_int
\int_new:N \l__tblr_key_remainder_int

%% #1: data name; #2: array position;
%% #3: returning tl with index; #4: returning tl with key name
\cs_new:Npn \__tblr_data_int_to_key:nnNN #1 #2 #3 #4
  {
    \int_set_eq:Nc \l__tblr_key_count_int { g__tblr_data_#1_key_count_int }
    \int_set:Nn \l__tblr_key_quotient_int
      {
        \int_div_truncate:nn
          { #2 + \l__tblr_key_count_int - 1 } { \l__tblr_key_count_int }
      }
    \int_set:Nn \l__tblr_key_remainder_int
      {
        #2 + \l__tblr_key_count_int
          - \l__tblr_key_quotient_int * \l__tblr_key_count_int
      }
    \int_compare:nNnT { \l__tblr_key_remainder_int } = { 0 }
      { \int_set_eq:NN \l__tblr_key_remainder_int \l__tblr_key_count_int }
    \tl_set:Ne #3 { \int_use:N \l__tblr_key_quotient_int }
    \tl_set_eq:Nc #4
      { c__tblr_data_#1_key_name_ \int_use:N \l__tblr_key_remainder_int _tl }
  }

%% #1: data name; #2: array position;
%% #3: returning tl with index 1; #4: returning tl with index 2;
%% #5: returning tl with key name
\cs_new:Npn \__tblr_data_int_to_key:nnNNN #1 #2 #3 #4 #5
  {
    \int_set_eq:Nc \l__tblr_key_count_int { g__tblr_data_#1_key_count_int }
    \int_set:Nn \l__tblr_key_quotient_int
      {
        \int_div_truncate:nn
          { #2 + \l__tblr_key_count_int - 1 } { \l__tblr_key_count_int }
      }
    \int_set:Nn \l__tblr_key_remainder_int
      {
        #2 + \l__tblr_key_count_int
          - \l__tblr_key_quotient_int * \l__tblr_key_count_int
      }
    \int_compare:nNnT { \l__tblr_key_remainder_int } = { 0 }
      { \int_set_eq:NN \l__tblr_key_remainder_int \l__tblr_key_count_int }
    \tl_set_eq:Nc #5
      { c__tblr_data_#1_key_name_ \int_use:N \l__tblr_key_remainder_int _tl }
    \int_set:Nn \l__tblr_key_quotient_two_int
      {
        \int_div_truncate:nn
          { \l__tblr_key_quotient_int + \c@colcount - 1 } { \c@colcount }
      }
    \int_set:Nn \l__tblr_key_remainder_int
      {
        \l__tblr_key_quotient_int + \c@colcount
          - \l__tblr_key_quotient_two_int * \c@colcount
      }
    \int_compare:nNnT { \l__tblr_key_remainder_int } = { 0 }
      { \int_set_eq:NN \l__tblr_key_remainder_int \c@colcount }
    \tl_set:Ne #4 { \int_use:N \l__tblr_key_remainder_int }
    \tl_set:Ne #3 { \int_use:N \l__tblr_key_quotient_two_int }
  }

\tl_new:N \g__tblr_data_int_from_value_tl

%% #1: data name; #2: key name; #3: value
%% The result will be stored in \g__tblr_data_int_from_value_tl
\cs_new_protected:Npn \__tblr_data_int_from_value:nnn #1 #2 #3
  {
    \cs:w
      __tblr_data_int_from_ \tl_use:c { c__tblr_data_#1_key_type_#2_tl } :n
    \cs_end:
    {#3}
  }

%% #1: data name; #2: key name; #3: int
\cs_new:Npn \__tblr_data_int_to_value:nnn #1 #2 #3
  {
    \cs:w
      __tblr_data_int_to_ \tl_use:c { c__tblr_data_#1_key_type_#2_tl } :n
    \cs_end:
    {#3}
  }
\cs_generate_variant:Nn \__tblr_data_int_to_value:nnn { nne, nVe }

\cs_new_protected:Npn \__tblr_data_int_from_int:n #1
  {
    \tl_gset:Nn \g__tblr_data_int_from_value_tl {#1}
  }

\cs_new:Npn \__tblr_data_int_to_int:n #1
  {
    #1
  }

\cs_new_protected:Npn \__tblr_data_int_from_dim:n #1
  {
    \tl_gset:Ne \g__tblr_data_int_from_value_tl { \dim_to_decimal_in_sp:n {#1} }
  }

%% Return a dimension in pt so that it's easier to understand in tracing messages
\cs_new:Npn \__tblr_data_int_to_dim:n #1
  {
    %#1 sp
    %\dim_eval:n { #1 sp }
    \dim_to_decimal:n { #1 sp } pt
  }

\cs_new_protected:Npn \__tblr_data_int_from_dec:n #1
  {
    \tl_gset:Ne \g__tblr_data_int_from_value_tl
      { \dim_to_decimal_in_sp:n {#1 pt} }
  }

\cs_new:Npn \__tblr_data_int_to_dec:n #1
  {
    \dim_to_decimal:n {#1 sp}
  }

\int_new:N \g__tblr_data_str_value_count_int
\tl_gclear_new:c { g__tblr_data_0_to_str_tl }

\cs_new_protected:Npn \__tblr_data_int_from_str:n #1
  {
    \tl_if_exist:cTF { g__tblr_data_ \tl_to_str:n {#1} _to_int_tl }
      {
        \tl_gset_eq:Nc \g__tblr_data_int_from_value_tl
          { g__tblr_data_ \tl_to_str:n {#1} _to_int_tl }
      }
      {
        \int_gincr:N \g__tblr_data_str_value_count_int
        \tl_new:c { g__tblr_data_ \tl_to_str:n {#1} _to_int_tl }
        \tl_gset:ce { g__tblr_data_ \tl_to_str:n {#1} _to_int_tl }
          { \int_use:N \g__tblr_data_str_value_count_int }
        \tl_new:c
          { g__tblr_data_ \int_use:N \g__tblr_data_str_value_count_int _to_str_tl }
        \tl_gset:cn
          { g__tblr_data_ \int_use:N \g__tblr_data_str_value_count_int _to_str_tl }
          { \exp_not:n {#1} }
        \tl_gset:Ne \g__tblr_data_int_from_value_tl
          { \int_use:N \g__tblr_data_str_value_count_int }
      }
  }

\cs_new:Npn \__tblr_data_int_to_str:n #1
  {
    \tl_use:c { g__tblr_data_#1_to_str_tl }
  }

%% #1: data name; #2: data index; #3: key; #4: value
\cs_new_protected:Npn \__tblr_data_gput:nnnn #1 #2 #3 #4
  {
    \__tblr_data_int_from_value:nnn {#1} {#3} {#4}
    \__tblr_intarray_gset:cnn
      { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
      { \__tblr_data_key_to_int:nnn {#1} {#2} {#3} }
      { \g__tblr_data_int_from_value_tl }
  }
\cs_generate_variant:Nn \__tblr_data_gput:nnnn
  { nnne, nnnV, nenn, nene, nenV, nVnn }

%% #1: data name; #2: data index 1; #3: data index 2; #4: key; #5: value
\cs_new_protected:Npn \__tblr_data_gput:nnnnn #1 #2 #3 #4 #5
  {
    \__tblr_data_int_from_value:nnn {#1} {#4} {#5}
    \__tblr_intarray_gset:cnn
      { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
      { \__tblr_data_key_to_int:nnnn {#1} {#2} {#3} {#4} }
      { \g__tblr_data_int_from_value_tl }
  }
\cs_generate_variant:Nn \__tblr_data_gput:nnnnn
  { nnnne, nnnnV, neenn, neene, neenV, neeen, nVVnn }

%% #1: data name; #2: data index; #3: key
\cs_new:Npn \__tblr_data_item:nnn #1 #2 #3
  {
    \__tblr_data_int_to_value:nne {#1} {#3}
      {
        \intarray_item:cn { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
          { \__tblr_data_key_to_int:nnn {#1} {#2} {#3} }
      }
  }
\cs_generate_variant:Nn \__tblr_data_item:nnn { nen }

%% #1: data name; #2: data index 1; #3: data index 2; #4: key
\cs_new:Npn \__tblr_data_item:nnnn #1 #2 #3 #4
  {
    \__tblr_data_int_to_value:nne {#1} {#4}
      {
        \intarray_item:cn { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
          { \__tblr_data_key_to_int:nnnn {#1} {#2} {#3} {#4} }
      }
  }
\cs_generate_variant:Nn \__tblr_data_item:nnnn { neen }

\tl_new:N \l__tblr_data_key_tl
\tl_new:N \l__tblr_data_index_tl
\tl_new:N \l__tblr_data_index_two_tl

\cs_new_protected:Npn \__tblr_data_log:n #1
  {
    \use:c { __tblr_data_log_ \use:c { c__tblr_data_#1_index_number_tl } :n } {#1}
    \__tblr_prop_log:n {#1}
  }

\cs_new_protected:cpn { __tblr_data_log_1:n } #1
  {
    %\intarray_log:c { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
    \tl_set:Ne \l_tmpa_tl { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
    \tl_log:n { ----------~----------~----------~----------~---------- }
    \int_step_inline:nn
      { \intarray_count:c { \l_tmpa_tl } }
      {
        \__tblr_data_int_to_key:nnNN {#1} {##1}
          \l__tblr_data_index_tl \l__tblr_data_key_tl
        \tl_log:e
          {
            \space
            { #1 [\l__tblr_data_index_tl] / \l__tblr_data_key_tl }
            ~\space => ~\space
            {
              \__tblr_data_int_to_value:nVe {#1} \l__tblr_data_key_tl
                { \intarray_item:cn { \l_tmpa_tl } {##1} }
            }
          }
      }
  }

\cs_new_protected:cpn { __tblr_data_log_2:n } #1
  {
    %\intarray_log:c { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
    \tl_set:Ne \l_tmpa_tl { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
    \tl_log:n { ----------~----------~----------~----------~---------- }
    \int_step_inline:nn
      { \intarray_count:c { \l_tmpa_tl } }
      {
        \__tblr_data_int_to_key:nnNNN {#1} {##1}
          \l__tblr_data_index_tl \l__tblr_data_index_two_tl \l__tblr_data_key_tl
        \tl_log:e
          {
            \space
            {
              #1 [\l__tblr_data_index_tl][\l__tblr_data_index_two_tl]
                 / \l__tblr_data_key_tl
            }
            ~\space => ~\space
            {
              \__tblr_data_int_to_value:nVe {#1} \l__tblr_data_key_tl
                { \intarray_item:cn { \l_tmpa_tl } {##1} }
            }
          }
      }
  }

%% #1: data name; #2: row index; #3: key; #4: value
\cs_new_protected:Npn \__tblr_data_gput_if_larger:nnnn #1 #2 #3 #4
  {
    \__tblr_data_int_from_value:nnn {#1} {#3} {#4}
    \__tblr_array_gput_if_larger:cnn
      { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
      { \__tblr_data_key_to_int:nnn {#1} {#2} {#3} }
      { \g__tblr_data_int_from_value_tl }
  }
\cs_generate_variant:Nn \__tblr_data_gput_if_larger:nnnn { nnne, nnnV, nene, nenV }

\cs_new_protected:Npn \__tblr_array_gput_if_larger:Nnn #1 #2 #3
  {
    \int_compare:nNnT {#3} > { \intarray_item:Nn #1 {#2} }
      { \__tblr_intarray_gset:Nnn #1 {#2} {#3} }
  }
\cs_generate_variant:Nn \__tblr_array_gput_if_larger:Nnn { cnn }

%% #1: data name; #2: data index; #3: key; #4: value
\cs_new_protected:Npn \__tblr_data_gadd_dimen_value:nnnn #1 #2 #3 #4
  {
    \__tblr_data_int_from_value:nnn {#1} {#3} {#4}
    \__tblr_array_gadd_value:cnn
      { g__tblr_#1_ \int_use:N \gTblrLevelInt _intarray }
      { \__tblr_data_key_to_int:nnn {#1} {#2} {#3} }
      { \g__tblr_data_int_from_value_tl }
  }
\cs_generate_variant:Nn \__tblr_data_gadd_dimen_value:nnnn
  { nnne, nnnV, nenn, nene }

\cs_new_protected:Npn \__tblr_array_gadd_value:Nnn #1 #2 #3
  {
    \__tblr_intarray_gset:Nnn #1 {#2} { \intarray_item:Nn #1 {#2} + #3 }
  }
\cs_generate_variant:Nn \__tblr_array_gadd_value:Nnn { cnn }

%%% --------------------------------------------------------
%%> \section{Switch between different data structures}
%%% --------------------------------------------------------

\cs_set_eq:NN \__tblr_prop_new:N \prop_new:N
\cs_set_eq:NN \__tblr_prop_gclear_new:N \prop_gclear_new:N
\cs_generate_variant:Nn \__tblr_prop_gclear_new:N { c }

\bool_new:N \g__tblr_use_intarray_bool
\bool_gset_true:N \g__tblr_use_intarray_bool

\cs_new_protected:Npn \__tblr_map_data_to_spec:
  {
    \cs_set_protected:Npn \__tblr_data_gput:nnnn ##1 ##2 ##3 ##4
      {
        \__tblr_spec_gput:nnn {##1} { [##2] / ##3 } {##4}
      }
    \cs_set_protected:Npn \__tblr_data_gput:nnnnn ##1 ##2 ##3 ##4 ##5
      {
        \__tblr_spec_gput:nnn {##1} { [##2][##3] / ##4 } {##5}
      }
    \cs_set:Npn \__tblr_data_item:nnn ##1 ##2 ##3
      {
        \__tblr_spec_item:nn {##1} { [##2] / ##3 }
      }
    \cs_set:Npn \__tblr_data_item:nnnn ##1 ##2 ##3 ##4
      {
        \__tblr_spec_item:nn {##1} { [##2][##3] / ##4 }
      }
    \cs_set_protected:Npn \__tblr_data_log:n ##1
      {
        \__tblr_spec_log:n {##1}
      }
    \cs_set_protected:Npn \__tblr_data_gput_if_larger:nnnn ##1 ##2 ##3 ##4
      {
        \__tblr_spec_gput_if_larger:nnn {##1} { [##2] / ##3 } {##4}
      }
    \cs_set_protected:Npn \__tblr_data_gput_if_larger:nnnnn ##1 ##2 ##3 ##4 ##5
      {
        \__tblr_spec_gput_if_larger:nnn {##1} { [##2][##3] / ##4 } {##5}
      }
    \cs_set_protected:Npn \__tblr_data_gadd_dimen_value:nnnn ##1 ##2 ##3 ##4
      {
        \__tblr_spec_gadd_dimen_value:nnn {##1} { [##2] / ##3 } {##4}
      }
    \cs_set_protected:Npn \__tblr_data_gadd_dimen_value:nnnnn ##1 ##2 ##3 ##4 ##5
      {
        \__tblr_spec_gadd_dimen_value:nnn {##1} { [##2][##3] / ##4 } {##5}
      }
  }

\bool_new:N \g__tblr_use_linked_prop_bool

\cs_new_protected:Npn \__tblr_map_data_to_prop:
  {
    \cs_set_protected:Npn \__tblr_data_gput:nnnn ##1 ##2 ##3 ##4
      {
        \__tblr_prop_gput:nnn {##1} { [##2] / ##3 } {##4}
      }
    \cs_set_protected:Npn \__tblr_data_gput:nnnnn ##1 ##2 ##3 ##4 ##5
      {
        \__tblr_prop_gput:nnn {##1} { [##2][##3] / ##4 } {##5}
      }
    \cs_set:Npn \__tblr_data_item:nnn ##1 ##2 ##3
      {
        % Be careful not to add \tl_log in an expandable function
        \__tblr_prop_item:nn {##1} { [##2] / ##3 }
      }
    \cs_set:Npn \__tblr_data_item:nnnn ##1 ##2 ##3 ##4
      {
        % Be careful not to add \tl_log in an expandable function
        \__tblr_prop_item:nn {##1} { [##2][##3] / ##4 }
      }
    \cs_set_protected:Npn \__tblr_data_log:n ##1
      {
        \__tblr_prop_log:n {##1}
      }
    \cs_set_protected:Npn \__tblr_data_gput_if_larger:nnnn ##1 ##2 ##3 ##4
      {
        \__tblr_prop_gput_if_larger:nnn {##1} { [##2] / ##3 } {##4}
      }
    \cs_set_protected:Npn \__tblr_data_gput_if_larger:nnnnn ##1 ##2 ##3 ##4 ##5
      {
        \__tblr_prop_gput_if_larger:nnn {##1} { [##2][##3] / ##4 } {##5}
      }
    \cs_set_protected:Npn \__tblr_data_gadd_dimen_value:nnnn ##1 ##2 ##3 ##4
      {
        \__tblr_prop_gadd_dimen_value:nnn {##1} { [##2] / ##3 } {##4}
      }
    \cs_set_protected:Npn \__tblr_data_gadd_dimen_value:nnnnn ##1 ##2 ##3 ##4 ##5
      {
        \__tblr_prop_gadd_dimen_value:nnn {##1} { [##2][##3] / ##4 } {##5}
      }
  }

\cs_new_protected:Npn \__tblr_map_spec_to_prop:
  {
    \cs_set_eq:NN \__tblr_spec_gput:nnn \__tblr_prop_gput:nnn
    \cs_set_eq:NN \__tblr_spec_item:nn \__tblr_prop_item:nn
    \cs_set_eq:NN \__tblr_spec_log:n \__tblr_prop_log:n
    \cs_set_eq:NN
      \__tblr_spec_gput_if_larger:nnn
      \__tblr_prop_gput_if_larger:nnn
    \cs_set_eq:NN
      \__tblr_spec_gadd_dimen_value:nnn
      \__tblr_prop_gadd_dimen_value:nnn
  }

%% Backport fix for https://github.com/latex3/latex3/issues/1630
\cs_new_protected:Npn \__tblr_backport_prop_item_fix:
  {
    \cs_set:Npn \prop_item:Nn ##1 ##2
      {
        \__prop_if_flat:NTF ##1
          {
            \exp_args:NNo \prop_map_tokens:Nn ##1
              {
                \exp_after:wN \__prop_item:nnn
                \exp_after:wN { \tl_to_str:n {##2} }
              }
          }
          { \exp_after:wN \__prop_get_linked:w ##1 {##2} \exp_not:n { } { } }
      }
  }

%% We can't use \IfExplAtLeastTF since it was added in LaTeX release 2023-11-01.
\@ifl@t@r \ExplLoaderFileDate { 2024-02-18 }
  { \bool_gset_true:N \g__tblr_use_linked_prop_bool } { }

\AtBeginDocument
  {
    \bool_if:NTF \g__tblr_use_linked_prop_bool
      {
        \__tblr_map_spec_to_prop:
        \__tblr_map_data_to_prop:
        \cs_set_eq:NN \__tblr_prop_new:N \prop_new_linked:N
        \cs_set_eq:NN \__tblr_prop_gclear_new:N \prop_gclear_new_linked:N
        \@ifl@t@r \ExplLoaderFileDate { 2024-12-09 }
          { } { \__tblr_backport_prop_item_fix: }
      }
      { \bool_if:NF \g__tblr_use_intarray_bool { \__tblr_map_data_to_spec: } }
  }

%%% --------------------------------------------------------
%%> \section{Child indexers and child selectors}
%%% --------------------------------------------------------

\clist_new:N \lTblrUsedChildIndexerClist
\clist_new:N \lTblrUsedChildSelectorClist

\msg_new:nnn { tabularray } { invalid-child-indexer }
  { Child ~ indexer ~ name ~ '#1' ~ must ~ start ~ with ~ uppercase ~ letter. }

\msg_new:nnn { tabularray } { invalid-child-selector }
  { Child ~ selector ~ name ~ '#1' ~ must ~ start ~ with ~ lowercase ~ letter. }

\msg_new:nnn { tabularray } { used-child-indexer }
  { Child ~ indexer ~ name ~ '#1' ~ has ~ been ~ used. }

\msg_new:nnn { tabularray } { used-child-selector }
  { Child ~ selector ~ name ~ '#1' ~ has ~ been ~ used. }

\tl_new:N \l__tblr_child_arg_spec_tl

\NewDocumentCommand \NewTblrChildIndexer { m O{0} o m }
  {
    \__tblr_new_child_indexer_aux:ennn { \tl_trim_spaces:n {#1} } {#2} {#3} {#4}
  }

\cs_new_protected:Npn \__tblr_new_child_indexer_aux:nnnn #1 #2 #3 #4
  {
    \__tblr_if_head_upper:nTF { #1 }
      {
        \clist_if_in:NnTF \lTblrUsedChildIndexerClist { #1 }
          {
            \msg_error:nnn { tabularray } { used-child-indexer } { #1 }
            \clist_log:N \lTblrUsedChildIndexerClist
          }
          {
            \__tblr_make_xparse_arg_spec:nnN
              { #2 } { #3 } \l__tblr_child_arg_spec_tl
            \exp_args:NcV \NewDocumentCommand
              { __tblr_child_indexer_ #1 } \l__tblr_child_arg_spec_tl { #4 }
            \clist_put_right:Nn \lTblrUsedChildIndexerClist { #1 }
          }
      }
      { \msg_error:nnn { tabularray } { invalid-child-indexer } { #1 } }
  }
\cs_generate_variant:Nn \__tblr_new_child_indexer_aux:nnnn { ennn }

\NewDocumentCommand \NewTblrChildSelector { m O{0} o m }
  {
    \__tblr_new_child_selector_aux:ennn { \tl_trim_spaces:n {#1} } {#2} {#3} {#4}
  }
\cs_set_eq:NN \NewChildSelector \NewTblrChildSelector

\cs_new_protected:Npn \__tblr_new_child_selector_aux:nnnn #1 #2 #3 #4
  {
    \__tblr_if_head_lower:nTF { #1 }
      {
        \clist_if_in:NnTF \lTblrUsedChildSelectorClist { #1 }
          {
            \msg_error:nnn { tabularray } { used-child-selector } { #1 }
            \clist_log:N \lTblrUsedChildSelectorClist
          }
          {
            \__tblr_make_xparse_arg_spec:nnN
              { #2 } { #3 } \l__tblr_child_arg_spec_tl
            \exp_args:NcV \NewDocumentCommand
              { __tblr_child_selector_ #1 } \l__tblr_child_arg_spec_tl { #4 }
            \clist_put_right:Nn \lTblrUsedChildSelectorClist { #1 }
          }
      }
      { \msg_error:nnn { tabularray } { invalid-child-selector } { #1 } }
  }
\cs_generate_variant:Nn \__tblr_new_child_selector_aux:nnnn { ennn }

%% #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:Ne #3 { \prg_replicate:nn { #1 - 1 } { m } }
      }
  }

\int_new:N \lTblrChildTotalInt
\int_new:N \lTblrChildHtotalInt
\int_new:N \lTblrChildVtotalInt
\tl_new:N \lTblrChildIndexTl % may be <i> or {<i>}{<j>}
\clist_new:N \lTblrChildClist

\NewTblrChildIndexer {U}
  {
    \tl_set:Ne \lTblrChildIndexTl
      { \int_eval:n { \int_max:nn { \lTblrChildTotalInt - 5 } { 1 } } }
  }
\NewTblrChildIndexer {V}
  {
    \tl_set:Ne \lTblrChildIndexTl
      { \int_eval:n { \int_max:nn { \lTblrChildTotalInt - 4 } { 1 } } }
  }
\NewTblrChildIndexer {W}
  {
    \tl_set:Ne \lTblrChildIndexTl
      { \int_eval:n { \int_max:nn { \lTblrChildTotalInt - 3 } { 1 } } }
  }
\NewTblrChildIndexer {X}
  {
    \tl_set:Ne \lTblrChildIndexTl
      { \int_eval:n { \int_max:nn { \lTblrChildTotalInt - 2 } { 1 } } }
  }
\NewTblrChildIndexer {Y}
  {
    \tl_set:Ne \lTblrChildIndexTl
      { \int_eval:n { \int_max:nn { \lTblrChildTotalInt - 1 } { 1 } } }
  }
\NewTblrChildIndexer {Z} [1] [1]
  {
    \tl_set:Ne \lTblrChildIndexTl
      { \int_eval:n { \int_max:nn { \lTblrChildTotalInt + 1 - #1 } { 1 } } }
  }

\NewTblrChildSelector { odd } [1] [1]
  {
    \int_if_odd:nTF {#1}
      {
        \clist_set:Ne \lTblrChildClist
          { {#1} {2} {\int_use:N \lTblrChildTotalInt} }
      }
      {
        \clist_set:Ne \lTblrChildClist
          { {\int_eval:n {#1 + 1}} {2} {\int_use:N \lTblrChildTotalInt} }
      }
  }

\NewTblrChildSelector { even } [1] [2]
  {
    \int_if_even:nTF {#1}
      {
        \clist_set:Ne \lTblrChildClist
          { {#1} {2} {\int_use:N \lTblrChildTotalInt} }
      }
      {
        \clist_set:Ne \lTblrChildClist
          { {\int_eval:n {#1 + 1 }} {2} {\int_use:N \lTblrChildTotalInt} }
      }
  }

%% #1: step; #2: start index; #3: end index
\NewTblrChildSelector { every } [3] [1]
  {
    \clist_set:Ne \lTblrChildClist
      {
        {
          \int_compare:nNnTF {#2} < {0}
            { \int_eval:n { \lTblrChildTotalInt + 1 #2 } } {#2}
        }
        { #1 }
        {
          \int_compare:nNnTF {#3} < {0}
            { \int_eval:n { \lTblrChildTotalInt + 1 #3 } } {#3}
        }
      }
  }

\clist_new:N \l__tblr_child_whole_clist
\seq_new:N \l__child_spec_seq
\tl_new:N \l__tblr_child_item_head_tl

%% #1, str of child specifications; #2, total number of children.
%% The result will be put into \l__tblr_child_whole_clist
\cs_new_protected:Npn \__tblr_child_parse:nn #1 #2
  {
    \clist_clear:N \l__tblr_child_whole_clist
    \seq_set_split:Nnn \l__child_spec_seq {,} {#1}
    \seq_map_inline:Nn \l__child_spec_seq
      {
        \clist_clear:N \lTblrChildClist
        \int_set:Nn \lTblrChildTotalInt {#2}
        \tl_set:Ne \l__tblr_child_item_head_tl { \tl_head:n {##1} }
        \tl_if_eq:NnTF \l__tblr_child_item_head_tl {-}
          { \clist_set:Ne \lTblrChildClist { {1} {1} {#2} } }
          {
            \__tblr_tl_if_lower:VTF \l__tblr_child_item_head_tl
              {
                \__tblr_child_run_selector:n {##1}
              }
              {
                \__tblr_tl_if_upper:VTF \l__tblr_child_item_head_tl
                  {
                    \__tblr_child_split_item:n {##1}
                    \__tblr_child_run_indexer_from:
                    \__tblr_child_parse_indexer_to:N \__tblr_child_set_clist:
                  }
                  {
                    \__tblr_tl_if_digit:VTF \l__tblr_child_item_head_tl
                      {
                        \__tblr_child_split_item:n {##1}
                        \__tblr_child_parse_indexer_to:N \__tblr_child_set_clist:
                      }
                      {
                        % error
                      }
                  }
              }
          }
        \clist_put_right:NV \l__tblr_child_whole_clist \lTblrChildClist
      }
    %\clist_log:N \l__tblr_child_whole_clist
  }
\cs_generate_variant:Nn \__tblr_child_parse:nn { ne }

\cs_new_protected:Npn \__tblr_child_run_selector:n #1
  {
    \tl_rescan:ne { \cctab_select:N \c_code_cctab }
      { \c_backslash_str __tblr_child_selector_ #1 }
    \scan_stop: % for selectors with only optional arguments
  }

\cs_new_protected:Npn \__tblr_child_run_indexer:n #1
  {
    \tl_rescan:ne { \cctab_select:N \c_code_cctab }
      { \c_backslash_str __tblr_child_indexer_ #1 }
    \scan_stop: % for indexers with only optional arguments
  }
\cs_generate_variant:Nn \__tblr_child_run_indexer:n { V }

\seq_new:N \l__tblr_child_item_seq
\tl_new:N \l__tblr_child_from_tl
\tl_new:N \l__tblr_child_to_tl

\cs_new_protected:Npn \__tblr_child_split_item:n #1
  {
    \seq_set_split:Nnn  \l__tblr_child_item_seq {-} {#1}
    \tl_set:Ne \l__tblr_child_from_tl
      { \seq_item:Nn \l__tblr_child_item_seq {1} }
    \tl_set:Ne \l__tblr_child_to_tl
      { \seq_item:Nn \l__tblr_child_item_seq {2} }
  }

\cs_new_protected:Npn \__tblr_child_parse_indexer_to:N #1
  {
    \str_if_empty:NTF \l__tblr_child_to_tl
      { \clist_set:NV \lTblrChildClist \l__tblr_child_from_tl }
      {
        \__tblr_tl_if_digit:eF { \tl_head:N \l__tblr_child_to_tl }
          { \__tblr_child_run_indexer_to: }
        #1
      }
  }

\cs_new_protected:Npn \__tblr_child_run_indexer_from:
  {
    \__tblr_child_run_indexer:V \l__tblr_child_from_tl
    \tl_set_eq:NN \l__tblr_child_from_tl \lTblrChildIndexTl
  }

\cs_new_protected:Npn \__tblr_child_run_indexer_to:
  {
    \__tblr_child_run_indexer:V \l__tblr_child_to_tl
    \tl_set_eq:NN \l__tblr_child_to_tl \lTblrChildIndexTl
  }

\cs_new_protected:Npn \__tblr_child_set_clist:
  {
    \clist_set:Ne \lTblrChildClist
      {
        { \l__tblr_child_from_tl } { 1 } { \l__tblr_child_to_tl }
      }
  }

\clist_new:N \l__tblr_child_tuple_whole_clist

%% #1, str of child tuple specifications; #2 and #3, total numbers of children.
%% The result will be put into \l__tblr_child_tuple_whole_clist
\cs_new_protected:Npn \__tblr_child_parse_tuple:nnn #1 #2 #3
  {
    \clist_clear:N \l__tblr_child_tuple_whole_clist
    \seq_set_split:Nnn \l__child_spec_seq {,} {#1}
    \seq_map_inline:Nn \l__child_spec_seq
      {
        \clist_clear:N \lTblrChildClist
        \int_set:Nn \lTblrChildHtotalInt {#2}
        \int_set:Nn \lTblrChildVtotalInt {#3}
        \tl_if_head_is_group:nTF {##1}
          {
            \__tblr_child_split_item:n {##1}
            \__tblr_child_parse_indexer_to:N \__tblr_child_set_clist_tuple:
          }
          {
            \tl_set:Ne \l__tblr_child_item_head_tl { \tl_head:n {##1} }
            \__tblr_tl_if_upper:VTF \l__tblr_child_item_head_tl
              {
                \__tblr_child_split_item:n {##1}
                \__tblr_child_run_indexer_from:
                \__tblr_child_parse_indexer_to:N \__tblr_child_set_clist_tuple:
              }
              {
                \__tblr_tl_if_lower:VTF \l__tblr_child_item_head_tl
                  { \__tblr_child_run_selector:n {##1} }
                  {
                    % error
                  }
              }
          }
        \clist_put_right:NV \l__tblr_child_tuple_whole_clist \lTblrChildClist
      }
    %\clist_log:N \l__tblr_child_tuple_whole_clist
  }
\cs_generate_variant:Nn \__tblr_child_parse_tuple:nnn { nee }

\int_new:N \l__child_diff_h_int
\int_new:N \l__child_diff_v_int
\int_new:N \l__child_sign_h_int
\int_new:N \l__child_sign_v_int
\int_new:N \l__child_step_int

\cs_new_protected:Npn \__tblr_child_set_clist_tuple:
  {
    \int_set:Nn \l__child_diff_h_int
      {
          \exp_after:wN \use_i:nn \l__tblr_child_to_tl
        - \exp_after:wN \use_i:nn \l__tblr_child_from_tl
      }
    \int_set:Nn \l__child_diff_v_int
      {
          \exp_after:wN \use_ii:nn \l__tblr_child_to_tl
        - \exp_after:wN \use_ii:nn \l__tblr_child_from_tl
      }
    \int_set:Nn \l__child_sign_h_int { \int_sign:n { \l__child_diff_h_int } }
    \int_set:Nn \l__child_sign_v_int { \int_sign:n { \l__child_diff_v_int } }
    \int_set:Nn \l__child_step_int
      {
        \int_min:nn
          { \int_abs:n { \l__child_diff_h_int } }
          { \int_abs:n { \l__child_diff_v_int } }
      }
    \int_step_inline:nnn { 0 } { \l__child_step_int }
      {
        \clist_put_right:Ne \lTblrChildClist
          {
            {
              \int_eval:n
                {
                  \exp_after:wN \use_i:nn \l__tblr_child_from_tl
                  + ##1 * \l__child_sign_h_int
                }
            }
            {
              \int_eval:n
                {
                  \exp_after:wN \use_ii:nn \l__tblr_child_from_tl
                  + ##1 * \l__child_sign_v_int
                }
            }
          }
      }
  }

%% Map over child index lists. We only support two level nesting.

\cs_new_protected:Npn \__tblr_child_map_inline:n #1
  {
    \__tblr_child_map_inline_aux:Nn \__tblr_child_map_aux:n {#1}
  }

\cs_new_protected:Npn \__tblr_child_submap_inline:n #1
  {
    \__tblr_child_map_inline_aux:Nn \__tblr_child_submap_aux:n {#1}
  }

%% #1: function for storing the inline code; #2: the inline code.
\cs_new_protected:Npn \__tblr_child_map_inline_aux:Nn #1 #2
  {
    \cs_set_protected:Npn #1 ##1 { #2 }
    \exp_args:NV \clist_map_inline:nn \l__tblr_child_whole_clist
      {
        \tl_if_head_is_group:nTF { ##1 }
          { \int_step_inline:nnnn ##1 { #1 { ####1 } } } { #1 { ##1 } }
      }
  }

%% Get the first or last index of a child index list (used by 'endpos' key).

\cs_new_protected:Npn \__tblr_child_get_first:N #1
  {
    \tl_set:Ne #1 { \clist_item:Nn \l__tblr_child_whole_clist { 1 } }
    \exp_args:NV \tl_if_head_is_group:nT #1
      { \tl_set:Ne #1 { \exp_after:wN \use_i:nnn #1 } }
  }

\cs_new_protected:Npn \__tblr_child_get_last:N #1
  {
    \tl_set:Ne #1 { \clist_item:Nn \l__tblr_child_whole_clist { -1 } }
    \exp_args:NV \tl_if_head_is_group:nT #1
      { \exp_after:wN \__tblr_child_get_step_last:nnnN #1 #1 }
  }

%% #1: from; #2: step; #3: to; #4: tl var for storing the result
\cs_new_protected:Npn \__tblr_child_get_step_last:nnnN #1 #2 #3 #4
  {
    \tl_set:Ne #4 { \int_div_truncate:nn { #3 - #1 } { #2 } }
    \int_compare:nNnTF { #4 } > { 0 }
      { \tl_set:Ne #4 { \int_eval:n { #1 + #4 * #2 } } }
      { \tl_set:Nn #4 { #1 } }
  }

%%% --------------------------------------------------------
%%> \section{New table commands}
%%% --------------------------------------------------------

%% We need some commands to modify table/row/column/cell specifications.
%% These commands must be defined with \NewTblrTableCommand 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 ~ command ~ #1 already ~ defined! }

\NewDocumentCommand \NewTblrTableCommand { 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 }
        %% we can not use \cs_if_exist:NTF here (see issue #328)
        \__tblr_cs_if_defined:NTF #1
          {
            \cs_set_eq:cN { __tblr_table_command_ \cs_to_str:N #1 _saved:w } #1
          }
          {
            \exp_args:NcV \NewDocumentCommand
              { __tblr_table_command_ \cs_to_str:N #1 _saved:w } \l__tblr_a_tl { }
          }
        \tl_new:c { g__tblr_table_cmd_ \cs_to_str:N #1 _arg_numb_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_set_eq:NN \NewTableCommand \NewTblrTableCommand

\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 _saved: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@rownum {#1}
    \int_set:Nn \c@colnum {#2}
  }

%% Add \empty as a table command so that users can write \\\empty\hline (see #328)
\NewTblrTableCommand\empty{}

%% Table commands are defined only inside tblr environments,
%% but some packages such as csvsimple need to use them outside tblr environments,
%% therefore we define some of them first here.
\ProvideDocumentCommand \SetHlines  { o m m } {}
\ProvideDocumentCommand \SetHline   { o m m } {}
\ProvideDocumentCommand \SetVlines  { o m m } {}
\ProvideDocumentCommand \SetVline   { o m m } {}
\ProvideDocumentCommand \SetCells   { o m } {}
\ProvideDocumentCommand \SetCell    { o m } {}
\ProvideDocumentCommand \SetRows    { o m } {}
\ProvideDocumentCommand \SetRow     { o m } {}
\ProvideDocumentCommand \SetColumns { o m } {}
\ProvideDocumentCommand \SetColumn  { o m } {}

%%% --------------------------------------------------------
%%> \section{Child ids and child classes}
%%% --------------------------------------------------------

\NewTblrTableCommand \SetChild [1]
  {
    \__tblr_keys_set:nn { child/index } {#1}
  }

\__tblr_keys_define:nn { child/index }
  {
    id  .code:n = \__tblr_child_add_id:n {#1},
    idh .code:n = \__tblr_child_add_idh:n {#1},
    idv .code:n = \__tblr_child_add_idv:n {#1},
    id* .meta:n = { id = #1, idh = #1, idv = #1 },
    class  .code:n = \__tblr_child_add_class:n {#1},
    classh .code:n = \__tblr_child_add_classh:n {#1},
    classv .code:n = \__tblr_child_add_classv:n {#1},
    class* .meta:n = { class = #1, classh = #1, classv = #1 }
  }

\clist_new:N \l__tblr_child_id_clist
\clist_new:N \l__tblr_child_class_clist

\cs_new_protected:Npn \__tblr_child_add_id:n #1
  {
    \clist_if_in:NnF \l__tblr_child_id_clist {#1}
      {
        \clist_put_right:Nn \l__tblr_child_id_clist {#1}
        \tl_clear_new:c { l__tblr_child_id_#1_tl }
      }
    \tl_set:ce { l__tblr_child_id_#1_tl }
      { { \int_use:N \c@rownum } { \int_use:N \c@colnum } }
  }
\cs_new_protected:Npn \__tblr_child_add_idh:n #1
  {
    \clist_if_in:NnF \l__tblr_child_id_clist {#1h}
      {
        \clist_put_right:Nn \l__tblr_child_id_clist {#1h}
        \tl_clear_new:c { l__tblr_child_id_#1h_tl }
      }
    \tl_set:ce { l__tblr_child_id_#1h_tl } { \int_use:N \c@rownum }
  }
\cs_new_protected:Npn \__tblr_child_add_idv:n #1
  {
    \clist_if_in:NnF \l__tblr_child_id_clist {#1v}
      {
        \clist_put_right:Nn \l__tblr_child_id_clist {#1v}
        \tl_clear_new:c { l__tblr_child_id_#1v_tl }
      }
    \tl_set:ce { l__tblr_child_id_#1v_tl } { \int_use:N \c@colnum }
  }

\cs_new_protected:Npn \__tblr_child_add_class:n #1
  {
    \clist_if_in:NnTF \l__tblr_child_class_clist {#1}
      {
        \clist_put_right:ce { l__tblr_child_class_#1_clist }
          { { \int_use:N \c@rownum } { \int_use:N \c@colnum } }
      }
      {
        \clist_put_right:Nn \l__tblr_child_class_clist {#1}
        \clist_clear_new:c { l__tblr_child_class_#1_clist }
        \clist_set:ce { l__tblr_child_class_#1_clist }
          { { \int_use:N \c@rownum } { \int_use:N \c@colnum } }
      }
  }
\cs_new_protected:Npn \__tblr_child_add_classh:n #1
  {
    \clist_if_in:NnTF \l__tblr_child_class_clist {#1h}
      {
        \clist_put_right:ce { l__tblr_child_class_#1h_clist }
          { \int_use:N \c@rownum }
      }
      {
        \clist_put_right:Nn \l__tblr_child_class_clist {#1h}
        \clist_clear_new:c { l__tblr_child_class_#1h_clist }
        \clist_set:ce { l__tblr_child_class_#1h_clist }
          { \int_use:N \c@rownum }
      }
  }
\cs_new_protected:Npn \__tblr_child_add_classv:n #1
  {
    \clist_if_in:NnTF \l__tblr_child_class_clist {#1v}
      {
        \clist_put_right:ce { l__tblr_child_class_#1v_clist }
          { \int_use:N \c@colnum }
      }
      {
        \clist_put_right:Nn \l__tblr_child_class_clist {#1v}
        \clist_clear_new:c { l__tblr_child_class_#1v_clist }
        \clist_set:ce { l__tblr_child_class_#1v_clist }
          { \int_use:N \c@colnum }
      }
  }

\cs_new_protected:Npn \__tblr_child_split_table_before:
  {
    \clist_clear:N \l__tblr_child_id_clist
    \clist_clear:N \l__tblr_child_class_clist
  }

\cs_new_protected:Npn \__tblr_child_split_table_after:
  {
    \clist_map_inline:Nn \l__tblr_child_id_clist
      {
        \exp_args:Nne
        \NewTblrChildIndexer {##1}
          {
            \tl_set:Nn \exp_not:N \lTblrChildIndexTl
              { \use:c { l__tblr_child_id_##1_tl } }
          }
      }
    \clist_map_inline:Nn \l__tblr_child_class_clist
      {
        \exp_args:Nne
        \NewTblrChildSelector {##1}
          {
            \clist_set:Nn \exp_not:N \lTblrChildClist
              { \use:c { l__tblr_child_class_##1_clist } }
          }
      }
  }

\cs_new_protected:Npn \__tblr_child_extract_index_command:N #1
  {
    \tl_if_head_eq_meaning:VNT #1 \SetChild
      {
        \tl_set:Ne #1 { \tl_tail:N #1 }
        \__tblr_keys_set:ne { child/index } { \tl_head:N #1 }
        \tl_set:Ne #1 { \tl_tail:N #1 }
      }
  }

\NewExpandableDocumentCommand \ExpTblrChildId {m}
  {
    \use:c { l__tblr_child_id_#1_tl }
  }

\NewExpandableDocumentCommand \ExpTblrChildClass {m}
  {
    \use:c { l__tblr_child_class_#1_clist }
  }

%%% --------------------------------------------------------
%%> \section{New content commands}
%%% --------------------------------------------------------

%% We need to emulate or fix some commands such as \diagbox in other packages
%% These commands must be defined with \NewTblrContentCommand command
%% We only enable them inside tblr environment to avoid potential conflict

\clist_new:N \g__tblr_content_commands_clist

\msg_new:nnn { tabularray } { defined-content-command }
  { Content ~ command ~ #1 already ~ defined! }

\NewDocumentCommand \NewTblrContentCommand { m O{0} o m }
  {
    \clist_if_in:NnTF \g__tblr_content_commands_clist { #1 }
      {
        \msg_error:nnn { tabularray } { defined-content-command } { #1 }
        \clist_log:N \g__tblr_content_commands_clist
      }
      {
        \__tblr_make_xparse_arg_spec:nnN { #2 } { #3 } \l__tblr_a_tl
        \exp_args:NcV \NewDocumentCommand
          { __tblr_content_command_ \cs_to_str:N #1 :w } \l__tblr_a_tl { #4 }
        \clist_gput_right:Nn \g__tblr_content_commands_clist { #1 }
      }
  }
\cs_set_eq:NN \NewContentCommand \NewTblrContentCommand

\cs_new_protected:Npn \__tblr_enable_content_commands:
  {
    \clist_map_inline:Nn \g__tblr_content_commands_clist
      { \cs_set_eq:Nc ##1 { __tblr_content_command_ \cs_to_str:N ##1 :w } }
  }

%%% --------------------------------------------------------
%%> \section{New dash styles}
%%% --------------------------------------------------------

%% \NewTblrDashStyle commands

\dim_new:N \lTblrDefaultHruleWidthDim
\dim_new:N \lTblrDefaultVruleWidthDim
\dim_set:Nn \lTblrDefaultHruleWidthDim {0.4pt}
\dim_set:Nn \lTblrDefaultVruleWidthDim {0.4pt}

\prop_new:N \g__tblr_defined_hdash_styles_prop
\prop_new:N \g__tblr_defined_vdash_styles_prop

\prop_gset_from_keyval:Nn \g__tblr_defined_hdash_styles_prop
  { solid = \hrule height \lTblrDefaultHruleWidthDim }
\prop_gset_from_keyval:Nn \g__tblr_defined_vdash_styles_prop
  { solid = \vrule width \lTblrDefaultVruleWidthDim }

\NewDocumentCommand \NewTblrDashStyle { m m }
  {
    \seq_set_split:Nnn \l_tmpa_seq { ~ } {#2}
    \tl_set:Ne \l__tblr_a_tl { \seq_item:Nn \l_tmpa_seq {1} }
    \tl_set:Ne \l__tblr_b_tl { \seq_item:Nn \l_tmpa_seq {2} }
    \tl_set:Ne \l__tblr_c_tl { \seq_item:Nn \l_tmpa_seq {3} }
    \tl_set:Ne \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:nee {#1}
              { \dim_eval:n {\l__tblr_b_tl} } { \dim_eval:n {\l__tblr_d_tl} }
          }
      }
  }
\cs_set_eq:NN \NewDashStyle \NewTblrDashStyle

\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:Ne \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 \lTblrDefaultHruleWidthDim }
          \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:Ne \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 \lTblrDefaultVruleWidthDim }
          \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 { nee }

\cs_new_protected:Npn \__tblr_get_hline_dash_style:N #1
  {
    \tl_set:Ne \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:Ne \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 }
  }

\NewTblrDashStyle {dashed} {on ~ 2pt ~ off ~ 2pt}
\NewTblrDashStyle {dotted} {on ~ 0.4pt ~ off ~ 1pt}

%%% --------------------------------------------------------
%%> \section{Set hlines and vlines}
%%% --------------------------------------------------------

\quark_new:N \q__tblr_dash
\quark_new:N \q__tblr_text

%% \SetHlines command for setting every hline in the table
\NewTblrTableCommand \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@rowcount + 1 } }
      {
        \int_set:Nn \c@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
\tl_new:N \l__tblr_hline_leftpos_tl  % left position
\tl_new:N \l__tblr_hline_rightpos_tl % right position
\bool_new:N \l__tblr_hline_endpos_bool % whether set positions only for both ends

\NewTblrTableCommand \cline [2] [] { \SetHline [=] {#2} {#1} }

\NewTblrTableCommand \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
\NewTblrTableCommand \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:
    \__tblr_keys_set_groups:nnn { hline/inner } { text } {#3}
    % true if "text=" is set
    \tl_if_eq:NNF \l__tblr_hline_dash_tl \q__tblr_text
      {
        \__tblr_set_hline_num:n {#1}
        \tl_clear:N \l__tblr_hline_dash_tl
        \__tblr_keys_set:nn { hline/inner } { 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_child_parse:ne {#1} { \int_eval:n { \c@rowcount + 1 } }
    \__tblr_child_map_inline:n
      {
        \int_set:Nn \c@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:Ne \l__tblr_hline_count_tl
      { \__tblr_spec_item:ne { hline } { [\int_use:N \c@rownum] / @hline-count } }
    %% \l__tblr_hline_count_tl may be empty when rowspec has extra |'s
    \int_compare:nNnTF { \l__tblr_hline_count_tl + 0 } = {0}
      {
        \tl_set:Ne \l__tblr_hline_num_tl { 1 }
        \__tblr_spec_gput:nen { hline }
          { [\int_use:N \c@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:Ne \l__tblr_hline_count_tl
      { \int_eval:n { \l__tblr_hline_count_tl + 1 } }
    \__tblr_spec_gput:nee { hline }
      { [\int_use:N \c@rownum] / @hline-count } { \l__tblr_hline_count_tl }
    \tl_set_eq:NN \l__tblr_hline_num_tl \l__tblr_hline_count_tl
  }

\__tblr_keys_define:nn { hline/inner }
  {
    dash .code:n = \tl_set:Nn \l__tblr_hline_dash_tl { \q__tblr_dash #1 },
    text .code:n = \tl_set:Nn \l__tblr_hline_dash_tl { \q__tblr_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},
    leftpos  .code:n = \tl_set:Ne \l__tblr_hline_leftpos_tl {#1},
    rightpos .code:n = \tl_set:Ne \l__tblr_hline_rightpos_tl {#1},
    l        .meta:n = { leftpos = #1 },
    l     .default:n = { -0.8 },
    r        .meta:n = { rightpos = #1 },
    r     .default:n = { -0.8 },
    lr       .meta:n = { leftpos = #1, rightpos = #1 },
    lr    .default:n = { -0.8 },
    endpos .bool_set:N = \l__tblr_hline_endpos_bool,
    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 { \q__tblr_dash #1 } }
      {
        \__tblr_if_color_value:nTF {#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_child_parse:ne {#1} { \int_use:N \c@colcount }
    \__tblr_child_map_inline:n
      {
        % prevent expansion of vline text (see issue #303)
        \__tblr_set_hline_option:nnn { ##1 } { @dash } 
          { \exp_not:V \l__tblr_hline_dash_tl }
        \tl_if_empty:NF \l__tblr_hline_wd_tl
          {
            \__tblr_set_hline_option:nnn { ##1 } { wd } { \l__tblr_hline_wd_tl }
          }
        \tl_if_empty:NF \l__tblr_hline_fg_tl
          {
            \__tblr_set_hline_option:nnn { ##1 } { fg } { \l__tblr_hline_fg_tl }
          }
      }
    \tl_if_empty:NF \l__tblr_hline_leftpos_tl
      {
        \bool_if:NTF \l__tblr_hline_endpos_bool
          {
            \__tblr_child_get_first:N \l_tmpa_tl
            \__tblr_set_hline_option:nnn
              { \l_tmpa_tl } { leftpos } { \l__tblr_hline_leftpos_tl }
          }
          {
            \__tblr_child_map_inline:n
              {
                \__tblr_set_hline_option:nnn
                  { ##1 } { leftpos } { \l__tblr_hline_leftpos_tl }
              }
          }
      }
    \tl_if_empty:NF \l__tblr_hline_rightpos_tl
      {
        \bool_if:NTF \l__tblr_hline_endpos_bool
          {
            \__tblr_child_get_last:N \l_tmpb_tl
            \__tblr_set_hline_option:nnn
              { \l_tmpb_tl } { rightpos } { \l__tblr_hline_rightpos_tl }
          }
          {
            \__tblr_child_map_inline:n
              {
                \__tblr_set_hline_option:nnn
                  { ##1 } { rightpos } { \l__tblr_hline_rightpos_tl }
              }
          }
      }
  }

%% #1: column; #2: key; #3: value
\cs_new_protected_nopar:Npn \__tblr_set_hline_option:nnn #1 #2 #3
  {
    \__tblr_spec_gput:nee { hline }
      { [\int_use:N \c@rownum][#1](\l__tblr_hline_num_tl) / #2 } { #3 }
  }

\msg_new:nnn { tabularray } { obsolete-firsthline }
  { \firsthline ~ is ~ obsolete; ~ use ~ 'baseline=T' ~ instead. }

\msg_new:nnn { tabularray } { obsolete-lasthline }
  { \lasthline ~ is ~ obsolete; ~ use ~ 'baseline=B' ~ instead. }

\NewTblrTableCommand \firsthline [1] []
  {
    \msg_error:nn { tabularray } { obsolete-firsthline }
  }

\NewTblrTableCommand \lasthline [1] []
  {
    \msg_error:nn { tabularray } { obsolete-lasthline }
  }

%% \SetVlines command for setting every vline in the table
\NewTblrTableCommand \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 { \c@colcount + 1 }
      {
        \int_set:Nn \c@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
\tl_new:N \l__tblr_vline_abovepos_tl % above position
\tl_new:N \l__tblr_vline_belowpos_tl % below position

\NewTblrTableCommand \rline [2] [] { \SetVline [=] {#2} {#1} }

\NewTblrTableCommand \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
\NewTblrTableCommand \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:
    \__tblr_keys_set_groups:nnn { vline/inner } { text } {#3}
    % true if "text=" is set
    \tl_if_eq:NNF \l__tblr_vline_dash_tl \q__tblr_text
      {
        \__tblr_set_vline_num:n {#1}
        \tl_clear:N \l__tblr_vline_dash_tl
        \__tblr_keys_set:nn { vline/inner } { 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_child_parse:ne {#1} { \int_eval:n { \c@colcount + 1} }
    \__tblr_child_map_inline:n
      {
        \int_set:Nn \c@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:Ne \l__tblr_vline_count_tl
      { \__tblr_spec_item:ne { vline } { [\int_use:N \c@colnum] / @vline-count } }
    %% \l__tblr_vline_count_tl may be empty when colspec has extra |'s
    \int_compare:nNnTF { \l__tblr_vline_count_tl + 0 } = {0}
      {
        \tl_set:Ne \l__tblr_vline_num_tl { 1 }
        \__tblr_spec_gput:nen { vline }
          { [\int_use:N \c@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:Ne \l__tblr_vline_count_tl
      { \int_eval:n { \l__tblr_vline_count_tl + 1 } }
    \__tblr_spec_gput:nee { vline }
      { [\int_use:N \c@colnum] / @vline-count } { \l__tblr_vline_count_tl }
    \tl_set_eq:NN \l__tblr_vline_num_tl \l__tblr_vline_count_tl
  }

\__tblr_keys_define:nn { vline/inner }
  {
    dash .code:n = \tl_set:Nn \l__tblr_vline_dash_tl { \q__tblr_dash #1 },
    text .code:n = \tl_set:Nn \l__tblr_vline_dash_tl { \q__tblr_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},
    abovepos .code:n = \tl_set:Ne \l__tblr_vline_abovepos_tl {#1},
    belowpos .code:n = \tl_set:Ne \l__tblr_vline_belowpos_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 { \q__tblr_dash #1 } }
      {
        \__tblr_if_color_value:nTF {#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_child_parse:ne {#1} { \int_use:N \c@rowcount }
    \__tblr_child_map_inline:n
      {
        \__tblr_spec_gput:nee { vline }
          { [##1][\int_use:N \c@colnum](\l__tblr_vline_num_tl) / @dash }
          % prevent expansion of vline text (see issue #303)
          { \exp_not:V \l__tblr_vline_dash_tl }
        \tl_if_empty:NF \l__tblr_vline_wd_tl
          {
            \__tblr_spec_gput:nee { vline }
              { [##1][\int_use:N \c@colnum](\l__tblr_vline_num_tl) / wd }
              { \l__tblr_vline_wd_tl }
          }
        \tl_if_empty:NF \l__tblr_vline_fg_tl
          {
            \__tblr_spec_gput:nee { vline }
              { [##1][\int_use:N \c@colnum](\l__tblr_vline_num_tl) / fg }
              { \l__tblr_vline_fg_tl }
          }
        \tl_if_empty:NF \l__tblr_vline_abovepos_tl
          {
            \__tblr_spec_gput:nee { vline }
              { [##1][\int_use:N \c@colnum](\l__tblr_vline_num_tl) / abovepos }
              { \l__tblr_vline_abovepos_tl }
          }
        \tl_if_empty:NF \l__tblr_vline_belowpos_tl
          {
            \__tblr_spec_gput:nee { vline }
              { [##1][\int_use:N \c@colnum](\l__tblr_vline_num_tl) / belowpos }
              { \l__tblr_vline_belowpos_tl }
          }
      }
  }

%%% --------------------------------------------------------
%%>  \section{Set hborders and vborders}
%%% --------------------------------------------------------

%% Hborder holds keys not related to a specified hline
\NewTblrTableCommand \hborder [1] { \tblr_set_hborder:n {#1} }

\cs_new_protected:Npn \tblr_set_hborder:n #1
  {
    \__tblr_keys_set:nn { hborder/inner } {#1}
  }

\cs_new_protected:Npn \tblr_set_hborder:nn #1 #2
  {
    \group_begin:
    \__tblr_child_parse:ne {#1} { \int_eval:n { \c@rowcount + 1 } }
    \__tblr_child_map_inline:n
      {
        \int_set:Nn \c@rownum {##1}
        \tblr_set_hborder:n {#2}
      }
    \group_end:
  }

%% This function is called when parsing table specifications
%% Note that #1 always includes an outer pair of braces
\cs_new_protected:Npn \__tblr_set_hborder_aux:nn #1 #2
  {
    \tblr_set_hborder:nn #1 {#2}
  }
\cs_generate_variant:Nn \__tblr_set_hborder_aux:nn { Vn }

\__tblr_keys_define:nn { hborder/inner }
  {
    abovespace .code:n = \__tblr_row_gput_above:ne
                          { belowsep } { \dim_eval:n {#1} },
    belowspace .code:n = \__tblr_row_gput:ne { abovesep } { \dim_eval:n {#1} },
    abovespace+ .code:n = \__tblr_row_gadd_dimen_above:ne
                          { belowsep } { \dim_eval:n {#1} },
    belowspace+ .code:n = \__tblr_row_gadd_dimen:ne
                          { abovesep } { \dim_eval:n {#1} },
    pagebreak   .code:n = \__tblr_hborder_gput_pagebreak:n {#1},
    pagebreak   .default:n = yes,
    baseline    .code:n = \__tblr_outer_gput_spec:ne
                          { baseline } { - \int_use:N \c@rownum },
  }

\tl_const:Nn \c__tblr_pagebreak_yes_tl  {  1 }
\tl_const:Nn \c__tblr_pagebreak_auto_tl {  0 }
\tl_const:Nn \c__tblr_pagebreak_no_tl   { -1 }

\cs_new_protected:Npn \__tblr_hborder_gput_pagebreak:n #1
  {
    \tl_if_exist:cT { c__tblr_pagebreak_ #1 _tl }
      {
        \__tblr_spec_gput:nee { hline }
          { [\int_use:N \c@rownum] / @pagebreak }
          { \tl_use:c { c__tblr_pagebreak_ #1 _tl } }
      }
  }

%% Vborder holds keys not related to a specified vline
\NewTblrTableCommand \vborder [1] { \tblr_set_vborder:n {#1} }

\cs_new_protected:Npn \tblr_set_vborder:n #1
  {
    \__tblr_keys_set:nn { vborder/inner } {#1}
  }

\cs_new_protected:Npn \tblr_set_vborder:nn #1 #2
  {
    \group_begin:
    \__tblr_child_parse:ne {#1} { \int_eval:n { \c@colcount + 1 } }
    \__tblr_child_map_inline:n
      {
        \int_set:Nn \c@colnum {##1}
        \tblr_set_vborder:n {#2}
      }
    \group_end:
  }

%% This function is called when parsing table specifications
%% Note that #1 always includes an outer pair of braces
\cs_new_protected:Npn \__tblr_set_vborder_aux:nn #1 #2
  {
    \tblr_set_vborder:nn #1 {#2}
  }
\cs_generate_variant:Nn \__tblr_set_vborder_aux:nn { Vn }

\__tblr_keys_define:nn { vborder/inner }
  {
    leftspace .code:n = \__tblr_column_gput_left:ne
                          { rightsep } { \dim_eval:n {#1} },
    rightspace .code:n = \__tblr_column_gput:ne { leftsep } { \dim_eval:n {#1} },
    leftspace+ .code:n = \__tblr_column_gadd_dimen_left:ne
                          { rightsep } { \dim_eval:n {#1} },
    rightspace+ .code:n = \__tblr_column_gadd_dimen:ne
                          { leftsep } { \dim_eval:n {#1} },
  }

%%% --------------------------------------------------------
%%> \section{Set cells}
%%% --------------------------------------------------------

%% \SetCells command for setting every cell in the table
\NewTblrTableCommand \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@rowcount }
      {
        \int_set:Nn \c@rownum {##1}
        \int_step_inline:nn { \c@colcount }
          {
            \int_set:Nn \c@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

\NewTblrTableCommand \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 }
    \__tblr_keys_set:nn { cell/outer } { #1 }
    \__tblr_keys_set:nn { cell/inner } { #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_child_parse:ne {#1} { \int_use:N \c@rowcount }
    \__tblr_child_map_inline:n
      {
        \int_set:Nn \c@rownum {##1}
        \__tblr_child_parse:ne {#2} { \int_use:N \c@colcount }
        \__tblr_child_submap_inline:n
          {
            \int_set:Nn \c@colnum {####1}
            \tblr_set_cell:nn {#3} {#4}
          }
      }
    \group_end:
  }

\cs_new_protected:Npn \__tblr_set_cell_tuple:nnn #1 #2 #3
  {
    \group_begin:
    \__tblr_child_parse_tuple:nee {#1}
      { \int_use:N \c@rowcount } { \int_use:N \c@colcount }
    \clist_map_inline:Nn \l__tblr_child_tuple_whole_clist
      {
        \int_set:Nn \c@rownum { \use_i:nn ##1 }
        \int_set:Nn \c@colnum { \use_ii:nn ##1 }
        \tblr_set_cell:nn {#2} {#3}
      }
    \group_end:
  }

%% Check the number of arguments and call \tblr_set_cell in different ways.
%% #1 consists of either two tl items {h_1,h_2,...,h_m}{v_1,v_2,...,v_n},
%% or one tl item {{h_1}{v_1},{h_2}{v_2},...,{h_k}{v_k}}.
%% This function is called when parsing table specifications.
\cs_new_protected:Npn \__tblr_set_cell_aux:nn #1 #2
  {
    \tl_if_single:nTF {#1}
      {
        \tl_if_head_is_group:nTF {#2}
          { \__tblr_set_cell_tuple:nnn #1 #2 }
          { \__tblr_set_cell_tuple:nnn #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 }

\__tblr_keys_define:nn { cell/outer }
  {
    r .tl_set:N = \l__tblr_row_span_num_tl,
    c .tl_set:N = \l__tblr_col_span_num_tl,
  }

\__tblr_keys_define:nn { cell/inner }
  {
    halign  .code:n = \__tblr_cell_gput:nn { halign } {#1},
    valign  .code:n = \__tblr_cell_gput:nn { valign } {#1},
    j       .meta:n = { halign = j },
    l       .meta:n = { halign = l },
    c       .meta:n = { halign = c },
    r       .meta:n = { halign = r },
    t       .meta:n = { valign = t },
    p       .meta:n = { valign = t },
    m       .meta:n = { valign = m },
    b       .meta:n = { valign = b },
    h       .meta:n = { valign = h },
    f       .meta:n = { valign = f },
    wd      .code:n = \__tblr_cell_gput:ne { width } {#1},
    bg      .code:n = \__tblr_cell_gput:ne { background } {#1},
    fg      .code:n = \__tblr_cell_gput:ne { foreground } {#1},
    font    .code:n = \__tblr_cell_gput:nn { font } { #1 \selectfont },
    mode    .code:n = \__tblr_cell_gput:nn { mode } {#1},
    $       .meta:n = { mode = math },
    $$      .meta:n = { mode = dmath },
    cmd     .code:n = \__tblr_cell_gput:nn { cmd } {#1},
    preto   .code:n = \__tblr_cell_preto_text:n {#1},
    appto   .code:n = \__tblr_cell_appto_text:n {#1},
    unknown .code:n = \__tblr_cell_unknown_key:V \l_keys_key_str,
  }

\cs_new_protected:Npn \__tblr_cell_gput:nn #1 #2
  {
    \__tblr_data_gput:neenn { cell }
      { \int_use:N \c@rownum } { \int_use:N \c@colnum } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_cell_gput:nn { ne }

\cs_new_protected:Npn \__tblr_cell_gput:nnnn #1 #2 #3 #4
  {
    \__tblr_data_gput:nnnnn { cell } {#1} {#2} {#3} {#4}
  }
\cs_generate_variant:Nn \__tblr_cell_gput:nnnn
  { nenn, ennn, eenn, nene, enne, eene }

\tl_new:N \l__tblr_cell_text_tl

\cs_new_protected:Npn \__tblr_cell_preto_text:n #1
  {
    \__tblr_cell_preto_text:een
      { \int_use:N \c@rownum } { \int_use:N \c@colnum } {#1}
  }

\cs_new_protected:Npn \__tblr_cell_preto_text:nnn #1 #2 #3
  {
    \tl_set:Ne \l__tblr_cell_text_tl { \__tblr_spec_item:nn { text } { [#1][#2] } }
    \tl_put_left:Nn \l__tblr_cell_text_tl {#3}
    \__tblr_spec_gput:nnV { text } { [#1][#2] } \l__tblr_cell_text_tl
  }
\cs_generate_variant:Nn \__tblr_cell_preto_text:nnn { nen, enn, een }

\cs_new_protected:Npn \__tblr_cell_appto_text:n #1
  {
    \__tblr_cell_appto_text:een
      { \int_use:N \c@rownum } { \int_use:N \c@colnum } {#1}
  }

\cs_new_protected:Npn \__tblr_cell_appto_text:nnn #1 #2 #3
  {
    \tl_set:Ne \l__tblr_cell_text_tl { \__tblr_spec_item:ne { text } { [#1][#2] } }
    \tl_put_right:Nn \l__tblr_cell_text_tl {#3}
    \__tblr_spec_gput:neV { text } { [#1][#2] } \l__tblr_cell_text_tl
  }
\cs_generate_variant:Nn \__tblr_cell_appto_text:nnn { nen, enn, een }

\cs_new_protected:Npn \__tblr_cell_unknown_key:n #1
  {
    \__tblr_if_color_value:nTF {#1}
      {
        \__tblr_data_gput:neene { cell }
          { \int_use:N \c@rownum } { \int_use:N \c@colnum } { background } {#1}
      }
      {
        \tl_set_rescan:Nnn \l__tblr_v_tl {} {#1}
        \__tblr_data_gput:neene { cell }
          { \int_use:N \c@rownum } { \int_use:N \c@colnum } { width }
          { \dim_eval:n { \l__tblr_v_tl } }
      }
  }
\cs_generate_variant:Nn \__tblr_cell_unknown_key:n { V }

%% Whether to allow page breaks in the middle of multirow cells
\bool_new:N \lTblrCellBreakBool

\cs_new_protected:Npn \__tblr_set_span_spec:nn #1 #2
  {
    \int_compare:nNnT { #1 } > { 1 }
      {
        \__tblr_prop_gput:nnn { inner } { rowspan } { true }
        \__tblr_data_gput:neenn { cell }
          { \int_use:N \c@rownum } { \int_use:N \c@colnum } { rowspan } {#1}
      }
    \int_compare:nNnT { #2 } > { 1 }
      {
        \__tblr_prop_gput:nnn { inner } { colspan } { true }
        \__tblr_data_gput:neenn { cell }
          { \int_use:N \c@rownum } { \int_use:N \c@colnum } { colspan } {#2}
      }
    \int_step_variable:nnNn
      { \int_use:N \c@rownum } { \int_eval:n { \c@rownum + #1 - 1 } } \l__tblr_i_tl
      {
        \bool_lazy_and:nnT
          { ! \lTblrCellBreakBool }
          { \int_compare_p:nNn {\l__tblr_i_tl} > {\c@rownum} }
          {
            \__tblr_spec_gput:nen {hline} { [\l__tblr_i_tl] / @pagebreak } {-1}
          }
        \int_step_variable:nnNn
          { \int_use:N \c@colnum } { \int_eval:n { \c@colnum + #2 - 1 } }
          \l__tblr_j_tl
          {
            \bool_lazy_and:nnF
              { \int_compare_p:nNn { \l__tblr_i_tl } = { \c@rownum } }
              { \int_compare_p:nNn { \l__tblr_j_tl } = { \c@colnum } }
              {
                \__tblr_data_gput:neenn { cell }
                  { \l__tblr_i_tl } { \l__tblr_j_tl } { omit } {1}
              }
            \int_compare:nNnF { \l__tblr_i_tl } = { \c@rownum }
              {
                \__tblr_spec_gput:nen { hline }
                  { [\l__tblr_i_tl][\l__tblr_j_tl] / omit } {true}
              }
            \int_compare:nNnF { \l__tblr_j_tl } = { \c@colnum }
              {
                \__tblr_spec_gput:nee { vline }
                  { [\l__tblr_i_tl][\l__tblr_j_tl] / omit } {true}
              }
          }
      }
    %% Make continuous borders for multirow cells
    \tl_set:Ne \l__tblr_n_tl
      {
        \int_max:nn
          {
            \__tblr_spec_item:ne { vline } { [\int_use:N \c@colnum] / @vline-count }
          }
          { 1 }
      }
    \int_step_variable:nnNn
      { \c@rownum } { \int_eval:n { \c@rownum + #1 - 2 } } \l__tblr_i_tl
      {
        \__tblr_spec_gput:nee { vline }
          { [\l__tblr_i_tl][\int_use:N \c@colnum](\l__tblr_n_tl) / belowpos } {1}
        \__tblr_spec_gput:nee { vline }
          { [\l__tblr_i_tl][\int_eval:n {\c@colnum + #2}](1) / belowpos } {1}
      }
  }
\cs_generate_variant:Nn \__tblr_set_span_spec:nn { VV }

%% Obsolete \multicolumn and \multirow commands

\msg_new:nnn { tabularray } { obsolete-multicolumn }
  { \multicolumn ~ is ~ obsolete; ~ use ~ \SetCell ~ instead. }

\msg_new:nnn { tabularray } { obsolete-multirow }
  { \multirow ~ is ~ obsolete; ~ use ~ \SetCell ~ instead. }

\NewTblrTableCommand \multicolumn [2]
  {
    \msg_error:nn { tabularray } { obsolete-multicolumn }
  }

\NewTblrTableCommand \multirow [3] [m]
  {
    \msg_error:nn { tabularray } { obsolete-multirow }
  }

%%% --------------------------------------------------------
%%> \section{Set columns and rows}
%%% --------------------------------------------------------

%% \SetColumns command for setting every column in the table
\NewTblrTableCommand \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@colcount }
      {
        \int_set:Nn \c@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

\NewTblrTableCommand \SetColumn [2] []
  {
    \tblr_set_column:nn {#1} {#2}
  }

\cs_new_protected:Npn \tblr_set_column:nn #1 #2
  {
    \__tblr_keys_set:nn { column/inner } {#2}
  }

\cs_new_protected:Npn \tblr_set_column:nnn #1 #2 #3
  {
    \group_begin:
    \__tblr_child_parse:ne {#1} { \int_use:N \c@colcount }
    \__tblr_child_map_inline:n
      {
        \int_set:Nn \c@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 }

\__tblr_keys_define:nn { column/inner }
  {
    halign    .code:n = \__tblr_column_gput_cell:nn { halign } {#1},
    valign    .code:n = \__tblr_column_gput_cell:nn { valign } {#1},
    j         .meta:n = { halign = j },
    l         .meta:n = { halign = l },
    c         .meta:n = { halign = c },
    r         .meta:n = { halign = r },
    t         .meta:n = { valign = t },
    p         .meta:n = { valign = t },
    m         .meta:n = { valign = m },
    b         .meta:n = { valign = b },
    h         .meta:n = { valign = h },
    f         .meta:n = { valign = f },
    bg        .code:n = \__tblr_column_gput_cell:nn { background } {#1},
    fg        .code:n = \__tblr_column_gput_cell:nn { foreground } {#1},
    font      .code:n = \__tblr_column_gput_cell:nn { font } { #1 \selectfont },
    mode      .code:n = \__tblr_column_gput_cell:nn { mode } {#1},
    $         .meta:n = { mode = math },
    $$        .meta:n = { mode = dmath },
    cmd       .code:n = \__tblr_column_gput_cell:nn { cmd } {#1},
    wd        .code:n = \__tblr_column_gput:ne { width } { \dim_eval:n {#1} },
    co        .code:n = \__tblr_column_gput:ne { coefficient } {#1},
    preto     .code:n = \__tblr_preto_text_for_every_column_cell:n {#1},
    appto     .code:n = \__tblr_appto_text_for_every_column_cell:n {#1},
    leftsep   .code:n = \__tblr_column_gput:ne { leftsep } { \dim_eval:n {#1} },
    rightsep  .code:n = \__tblr_column_gput:ne { rightsep } { \dim_eval:n {#1} },
    colsep    .meta:n = { leftsep = #1, rightsep = #1},
    leftsep+  .code:n = \__tblr_column_gadd_dimen:ne
                          { leftsep } { \dim_eval:n {#1} },
    rightsep+ .code:n = \__tblr_column_gadd_dimen:ne
                          { 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: key; #2: value
\cs_new_protected:Npn \__tblr_column_gput:nn #1 #2
  {
    \__tblr_data_gput:nenn { column } { \int_use:N \c@colnum } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_column_gput:nn { ne }

\cs_new_protected:Npn \__tblr_column_gput_left:nn #1 #2
  {
    \__tblr_data_gput:nenn { column } { \int_eval:n { \c@colnum - 1 } } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_column_gput_left:nn { ne }

\cs_new_protected:Npn \__tblr_column_gadd_dimen:nn #1 #2
  {
    \__tblr_data_gadd_dimen_value:nenn { column }
      { \int_use:N \c@colnum } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_column_gadd_dimen:nn { ne }

\cs_new_protected:Npn \__tblr_column_gadd_dimen_left:nn #1 #2
  {
    \__tblr_data_gadd_dimen_value:nenn { column }
      { \int_eval:n { \c@colnum - 1 } } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_column_gadd_dimen_left:nn { ne }

%% #1: key; #2: value
\cs_new_protected:Npn \__tblr_column_gput_cell:nn #1 #2
  {
    \int_step_inline:nn { \c@rowcount }
      {
        \__tblr_cell_gput:nenn {##1} { \int_use:N \c@colnum } {#1} {#2}
      }
  }
\cs_generate_variant:Nn \__tblr_column_gput_cell:nn { ne }

\cs_new_protected:Npn \__tblr_preto_text_for_every_column_cell:n #1
  {
    \int_step_inline:nn { \c@rowcount }
      {
        \__tblr_cell_preto_text:nen {##1} { \int_use:N \c@colnum } {#1}
      }
  }

\cs_new_protected:Npn \__tblr_appto_text_for_every_column_cell:n #1
  {
    \int_step_inline:nn { \c@rowcount }
      {
        \__tblr_cell_appto_text:nen {##1} { \int_use:N \c@colnum } {#1}
      }
  }

\cs_new_protected:Npn \__tblr_column_unknown_key:n #1
  {
    \__tblr_if_number_value:nTF {#1}
      { \__tblr_column_gput:ne { coefficient } {#1} }
      {
        \__tblr_if_color_value:nTF {#1}
          { \__tblr_column_gput_cell:nn { background } {#1} }
          {
            \tl_set_rescan:Nnn \l__tblr_v_tl {} {#1}
            \__tblr_column_gput:ne { 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
\NewTblrTableCommand \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@rowcount }
      {
        \int_set:Nn \c@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

\NewTblrTableCommand \SetRow [2] []
  {
    \tblr_set_row:nn {#1} {#2}
  }

\cs_new_protected:Npn \tblr_set_row:nn #1 #2
  {
    \__tblr_keys_set:nn { row/inner } {#2}
  }

\cs_new_protected:Npn \tblr_set_row:nnn #1 #2 #3
  {
    \group_begin:
    \__tblr_child_parse:ne {#1} { \int_use:N \c@rowcount }
    \__tblr_child_map_inline:n
      {
        \int_set:Nn \c@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 }

\__tblr_keys_define:nn { row/inner }
  {
    halign    .code:n = \__tblr_row_gput_cell:nn { halign } {#1},
    valign    .code:n = \__tblr_row_gput_cell:nn { valign } {#1},
    j         .meta:n = { halign = j },
    l         .meta:n = { halign = l },
    c         .meta:n = { halign = c },
    r         .meta:n = { halign = r },
    t         .meta:n = { valign = t },
    p         .meta:n = { valign = t },
    m         .meta:n = { valign = m },
    b         .meta:n = { valign = b },
    h         .meta:n = { valign = h },
    f         .meta:n = { valign = f },
    bg        .code:n = \__tblr_row_gput_cell:nn { background } {#1},
    fg        .code:n = \__tblr_row_gput_cell:nn { foreground } {#1},
    font      .code:n = \__tblr_row_gput_cell:nn { font } { #1 \selectfont },
    mode      .code:n = \__tblr_row_gput_cell:nn { mode } {#1},
    $         .meta:n = { mode = math },
    $$        .meta:n = { mode = dmath },
    cmd       .code:n = \__tblr_row_gput_cell:nn { cmd } {#1},
    ht        .code:n = \__tblr_row_gput:ne { height } { \dim_eval:n {#1} },
    co        .code:n = \__tblr_row_gput:ne { coefficient } {#1},
    preto     .code:n = \__tblr_preto_text_for_every_row_cell:n {#1},
    appto     .code:n = \__tblr_appto_text_for_every_row_cell:n {#1},
    abovesep  .code:n = \__tblr_row_gput:ne { abovesep } { \dim_eval:n {#1} },
    belowsep  .code:n = \__tblr_row_gput:ne { belowsep } { \dim_eval:n {#1} },
    rowsep    .meta:n = { abovesep = #1, belowsep = #1},
    abovesep+ .code:n = \__tblr_row_gadd_dimen:ne { abovesep } { \dim_eval:n {#1} },
    belowsep+ .code:n = \__tblr_row_gadd_dimen:ne { belowsep } { \dim_eval:n {#1} },
    rowsep+   .meta:n = { abovesep+ = #1, belowsep+ = #1},
    baseline  .code:n = \__tblr_outer_gput_spec:ne
                          { baseline } { \int_use:N \c@rownum },
    unknown   .code:n = \__tblr_row_unknown_key:V \l_keys_key_str,
  }

%% #1: key; #2: value
\cs_new_protected:Npn \__tblr_row_gput:nn #1 #2
  {
    \__tblr_data_gput:nenn { row } { \int_use:N \c@rownum } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_row_gput:nn { ne }

\cs_new_protected:Npn \__tblr_row_gput_above:nn #1 #2
  {
    \__tblr_data_gput:nenn { row } { \int_eval:n { \c@rownum - 1 } } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_row_gput_above:nn { ne }

\cs_new_protected:Npn \__tblr_row_gadd_dimen:nn #1 #2
  {
    \__tblr_data_gadd_dimen_value:nenn { row } { \int_use:N \c@rownum } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_row_gadd_dimen:nn { ne }

\cs_new_protected:Npn \__tblr_row_gadd_dimen_above:nn #1 #2
  {
    \__tblr_data_gadd_dimen_value:nenn { row }
      { \int_eval:n { \c@rownum - 1 } } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_row_gadd_dimen_above:nn { ne }

%% #1: key; #2: value
\cs_new_protected:Npn \__tblr_row_gput_cell:nn #1 #2
  {
    \int_step_inline:nn { \c@colcount }
      {
        \__tblr_cell_gput:ennn { \int_use:N \c@rownum } {##1} {#1} {#2}
      }
  }
\cs_generate_variant:Nn \__tblr_row_gput_cell:nn { ne }

\cs_new_protected:Npn \__tblr_preto_text_for_every_row_cell:n #1
  {
    \int_step_inline:nn { \c@colcount }
      {
        \__tblr_cell_preto_text:enn { \int_use:N \c@rownum } {##1} {#1}
      }
  }

\cs_new_protected:Npn \__tblr_appto_text_for_every_row_cell:n #1
  {
    \int_step_inline:nn { \c@colcount }
      {
        \__tblr_cell_appto_text:enn { \int_use:N \c@rownum } {##1} {#1}
      }
  }

\cs_new_protected:Npn \__tblr_row_unknown_key:n #1
  {
    \__tblr_if_number_value:nTF {#1}
      {
        \__tblr_data_gput:nene { row } { \int_use:N \c@rownum }
          { coefficient } {#1}
      }
      {
        \__tblr_if_color_value:nTF {#1}
          { \__tblr_row_gput_cell:nn { background } {#1} }
          {
            \tl_set_rescan:Nnn \l__tblr_v_tl {} {#1}
            \__tblr_row_gput:ne { height } { \dim_eval:n { \l__tblr_v_tl } }
          }
      }
  }
\cs_generate_variant:Nn \__tblr_row_unknown_key:n { V }

\NewTblrTableCommand \pagebreak [1] [4]
  {
    \hborder { pagebreak = yes }
  }

\NewTblrTableCommand \nopagebreak [1] [4]
  {
    \hborder { pagebreak = no }
  }

%%% --------------------------------------------------------
%%> \section{Column types and row types}
%%% --------------------------------------------------------

%% Some primitive column/row types

\str_const:Nn \cTblrPrimitiveColrowTypeStr { Q | < > }
\tl_new:N \g__tblr_expanded_colrow_spec_tl

\exp_args:Nc \NewDocumentCommand { tblr_primitive_Column_type_ Q } { O{} }
  {
    \__tblr_keys_set:nn { column/inner } { #1 }
    \int_incr:N \c@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{} }
  {
    \__tblr_keys_set:nn { row/inner } { #1 }
    \int_incr:N \c@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_data_gput:nene
          { column }
          { \int_use:N \c@colnum } { leftsep }
          { \dim_eval:n {#1} }
      }
    \tl_if_blank:nF {#2}
      {
        \__tblr_preto_text_for_every_column_cell:n {#2}
      }
    \__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_data_gput:nene { row } { \int_use:N \c@rownum }
          { abovesep } { \dim_eval:n { #1 } }
      }
    \tl_if_blank:nF {#2}
      {
        \__tblr_preto_text_for_every_row_cell:n {#2}
      }
    \__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_data_gput:nene { column }
          { \int_eval:n {\c@colnum - 1} } { rightsep } { \dim_eval:n {#1} }
      }
    \tl_if_blank:nF {#2}
      {
        \group_begin:
        \int_decr:N \c@colnum
        \__tblr_appto_text_for_every_column_cell:n {#2}
        \group_end:
      }
    \__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_data_gput:nene { row } { \int_eval:n {\c@rownum - 1} }
          { belowsep } { \dim_eval:n {#1} }
      }
    \tl_if_blank:nF {#2}
      {
        \group_begin:
        \int_decr:N \c@rownum
        \__tblr_appto_text_for_every_row_cell:n {#2}
        \group_end:
      }
    \__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 \gTblrUsedColumnTypeStr
\str_gset_eq:NN \gTblrUsedColumnTypeStr \cTblrPrimitiveColrowTypeStr

\str_new:N \gTblrUsedRowTypeStr
\str_gset_eq:NN \gTblrUsedRowTypeStr \cTblrPrimitiveColrowTypeStr

\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 \NewTblrColumnType { m O{0} o m }
  {
    \tl_gset:Nn \g__tblr_column_or_row_tl { Column }
    \__tblr_new_column_or_row_type:nnnn {#1} {#2} {#3} {#4}
  }
\cs_set_eq:NN \NewColumnType \NewTblrColumnType

\NewDocumentCommand \NewTblrRowType { m O{0} o m }
  {
    \tl_gset:Nn \g__tblr_column_or_row_tl { Row }
    \__tblr_new_column_or_row_type:nnnn {#1} {#2} {#3} {#4}
  }
\cs_set_eq:NN \NewRowType \NewTblrRowType

\NewDocumentCommand \NewTblrColumnRowType { m O{0} o m }
  {
    \tl_gset:Nn \g__tblr_column_or_row_tl { Column }
    \__tblr_new_column_or_row_type:nnnn {#1} {#2} {#3} {#4}
    \tl_gset:Nn \g__tblr_column_or_row_tl { Row }
    \__tblr_new_column_or_row_type:nnnn {#1} {#2} {#3} {#4}
  }
\cs_set_eq:NN \NewColumnRowType \NewTblrColumnRowType

\cs_new_protected:Npn \__tblr_new_column_or_row_type:nnnn #1 #2 #3 #4
  {
    \str_if_in:cnTF { gTblrUsed \g__tblr_column_or_row_tl TypeStr } {#1}
      {
        \tl_if_eq:NnTF \g__tblr_column_or_row_tl { Row }
          { \msg_error:nnnn { tabularray } { used-colrow-type } { Row } {#1} }
          { \msg_error:nnnn { tabularray } { used-colrow-type } { Column } {#1} }
        \str_log:c { gTblrUsed \g__tblr_column_or_row_tl TypeStr }
      }
      {
        \__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
          { gTblrUsed \g__tblr_column_or_row_tl TypeStr } {#1}
      }
  }

\NewTblrColumnRowType { l } { Q[l] }
\NewTblrColumnRowType { c } { Q[c] }
\NewTblrColumnRowType { r } { Q[r] }
\NewTblrColumnRowType { j } { Q[j] }

\NewTblrColumnType { t } [1] { Q[t,wd=#1] }
\NewTblrColumnType { p } [1] { Q[p,wd=#1] }
\NewTblrColumnType { m } [1] { Q[m,wd=#1] }
\NewTblrColumnType { b } [1] { Q[b,wd=#1] }
\NewTblrColumnType { h } [1] { Q[h,wd=#1] }
\NewTblrColumnType { f } [1] { Q[f,wd=#1] }

\NewTblrRowType { t } [1] { Q[t,ht=#1] }
\NewTblrRowType { p } [1] { Q[p,ht=#1] }
\NewTblrRowType { m } [1] { Q[m,ht=#1] }
\NewTblrRowType { b } [1] { Q[b,ht=#1] }
\NewTblrRowType { h } [1] { Q[h,ht=#1] }
\NewTblrRowType { f } [1] { Q[f,ht=#1] }

\NewTblrColumnRowType { X } [1][] { Q[co=1,#1] }

\NewTblrColumnRowType { ! } [1] { |[text={#1}] }
\NewTblrColumnRowType { @ } [1] { <[0pt]{} |[text={#1}] >[0pt]{} }
\NewTblrColumnRowType { * } [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 { gTblrUsed \g__tblr_column_or_row_tl TypeStr } {#1}
          {
            %% Note that #1 may be an active character (see issue #58)
            \cs:w tblr_ \g__tblr_column_or_row_tl _type_ \token_to_str:N #1 \cs_end:
          }
          {
            \msg_error:nnVn { tabularray } { unknown-colrow-type }
              \g__tblr_column_or_row_tl {#1}
            \str_log:c { gTblrUsed \g__tblr_column_or_row_tl TypeStr }
          }
      }
  }

%% 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@rownum {1} }
      { \int_set:Nn \c@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{Set environments and new environments}
%%% --------------------------------------------------------

\tl_new:N \l__tblr_initial_tblr_outer_tl
\tl_set:Nn \l__tblr_initial_tblr_outer_tl
  {
    halign = c, baseline = m, headsep = 6pt, footsep = 6pt,
    presep = 1.5\bigskipamount, postsep = 1.5\bigskipamount,
  }

%% #1: env name; #2: specifications
\NewDocumentCommand \SetTblrInner { O{tblr} m }
  {
    \clist_map_inline:nn {#1}
      { \tl_put_right:cn { l__tblr_default_ ##1 _inner_tl } { , #2 } }
    \ignorespaces
  }
\cs_new_eq:NN \SetTblrDefault \SetTblrInner

%% #1: env name; #2: specifications
\NewDocumentCommand \SetTblrOuter { O{tblr} m }
  {
    \clist_map_inline:nn {#1}
      { \tl_put_right:cn { l__tblr_default_ ##1 _outer_tl } { , #2 } }
    \ignorespaces
  }

%% #1: env name
\NewDocumentCommand \NewTblrEnviron { m }
  {
    \NewDocumentEnvironment {#1} { O{c} m +b }
      {
        \__tblr_environ_code:nnnn {#1} {##1} {##2} {##3}
      } { }
    \tl_new:c { l__tblr_default_ #1 _inner_tl }
    \tl_new:c { l__tblr_default_ #1 _outer_tl }
    \tl_set_eq:cN { l__tblr_default_ #1 _outer_tl } \l__tblr_initial_tblr_outer_tl
  }

%% Create tblr and longtblr environments
\NewTblrEnviron { tblr }
\NewTblrEnviron { longtblr }
\SetTblrOuter [ longtblr ] { long }
\NewTblrEnviron { talltblr }
\SetTblrOuter [ talltblr ] { tall }

\tl_new:N \l__tblr_env_name_tl
\bool_new:N \l__tblr_math_mode_bool

%% Main environment code
%% We need to add \group_align_safe_begin: and \group_align_safe_end:
%% to make tabularray correctly nest in align environment (see issue #143)
\cs_new_protected:Npn \__tblr_environ_code:nnnn #1 #2 #3 #4
  {
    \group_align_safe_begin:
    \int_gincr:N \c@tblrcount
    \tl_set:Nn \l__tblr_env_name_tl {#1}
    \mode_if_math:TF
      { \bool_set_true:N \l__tblr_math_mode_bool }
      { \bool_set_false:N \l__tblr_math_mode_bool }
    \__tblr_builder:nnn {#2} {#3} {#4}
    \group_align_safe_end:
  }

\bool_new:N \lTblrMeasuringBool

\tl_new:N \l__tblr_inner_spec_tl
\cs_set_eq:NN \__tblr_hook_parse_inner_spec_before: \prg_do_nothing:

%% Read, split and build the table
\cs_new_protected:Npn \__tblr_builder:nnn #1 #2 #3
  {
    \int_gincr:N \gTblrLevelInt
    \__tblr_hook_use:n { trial/before }
    \bool_set_true:N \lTblrMeasuringBool
    \__tblr_clear_prop_lists:
    \__tblr_clear_spec_lists:
    \LogTblrTracing { step = init ~ table ~ outer ~ spec}
    \__tblr_init_table_outer_spec:
    \LogTblrTracing { step = parse ~ table ~ options }
    \__tblr_parse_table_option:n {#1}
    \LogTblrTracing { outer }
    \LogTblrTracing { option }
    \__tblr_enable_table_commands:
    \LogTblrTracing { step = split ~ table}
    \__tblr_split_table:n {#3}
    \LogTblrTracing { command }
    \bool_if:NT \g__tblr_use_intarray_bool { \__tblr_init_table_data: }
    \LogTblrTracing { step = init ~ table ~ inner ~ spec}
    \__tblr_init_table_inner_spec:
    \LogTblrTracing { inner }
    \LogTblrTracing { step = parse ~ table ~ inner ~ spec}
    \tl_set:Nn \l__tblr_inner_spec_tl {#2}
    \__tblr_hook_parse_inner_spec_before:
    \exp_args:NV \__tblr_parse_table_spec:n \l__tblr_inner_spec_tl
    \LogTblrTracing { step = execute ~ table ~ commands}
    \__tblr_execute_table_commands:
    \__tblr_disable_table_commands:
    \__tblr_functional_calculation:
    \LogTblrTracing { step = calculate ~ cell ~ and ~ line ~ sizes}
    \__tblr_enable_content_commands:
    \__tblr_calc_cell_and_line_sizes:
    \bool_set_false:N \lTblrMeasuringBool
    \__tblr_hook_use:n { trial/after }
    \LogTblrTracing { step = build ~ the ~ whole ~ table}
    \__tblr_build_whole:
    \int_gdecr:N \gTblrLevelInt
  }

%%% --------------------------------------------------------
%%> \section{Split table contents}
%%% --------------------------------------------------------

\tl_new:N \l__tblr_body_tl
\seq_new:N \l__tblr_lines_seq

%% Split table content to cells and store them
%% #1: table content
\cs_new_protected:Npn \__tblr_split_table:n #1
  {
    \__tblr_child_split_table_before:
    \tl_set:Nn \l__tblr_body_tl {#1}
    \__tblr_modify_table_body:
    \int_zero:N \c@rowcount
    \int_zero:N \c@colcount
    \__tblr_split_table_to_lines:NN \l__tblr_body_tl \l__tblr_lines_seq
    \__tblr_split_lines_to_cells:N \l__tblr_lines_seq
    \__tblr_child_split_table_after:
  }

\tl_new:N \l__tblr_expand_tl

\cs_set_eq:NN \__tblr_hook_split_before: \prg_do_nothing:

\cs_new_protected:Npn \__tblr_modify_table_body:
  {
    \__tblr_hook_split_before:
    \tl_set:Ne \l__tblr_expand_tl { \__tblr_spec_item:nn { outer } { expand } }
    \tl_map_inline:Nn \l__tblr_expand_tl
      {
        \__tblr_expand_table_body:NN \l__tblr_body_tl ##1
      }
  }

%% Expand every occurrence of the specified macro once
%% #1: tl with table content; #2: macro to be expanded
\cs_new_protected:Npn \__tblr_expand_table_body:NN #1 #2
  {
    \tl_set_eq:NN \l_tmpa_tl #1
    \tl_clear:N #1
    \cs_set_protected:Npn \__tblr_expand_table_body_aux:w ##1 #2
      {
        \tl_put_right:Nn #1 {##1}
        \peek_meaning:NTF \q_stop
          { \use_none:n }
          { \exp_last_unbraced:NV \__tblr_expand_table_body_aux:w #2 }
      }
    \exp_last_unbraced:NV \__tblr_expand_table_body_aux:w \l_tmpa_tl #2 \q_stop
  }

%% Split tl #3 into items separated by tl #2, and store the result in seq #1.
%% Spaces on both side of each item and outer braces around each item are kept.
%% We insert \prg_do_nothing: before each item to avoid losing outermost braces.
\cs_new_protected:Npn \__tblr_seq_set_split:Nnn #1 #2 #3
  {
    \seq_clear:N #1
    \cs_set_protected:Npn \__tblr_seq_set_split_aux:Nw ##1 ##2 #2
      {
        \tl_if_eq:nnF { \prg_do_nothing: \c_novalue_tl } { ##2 }
          {
            \seq_put_right:No ##1 { ##2 }
            \__tblr_seq_set_split_aux:Nw ##1 \prg_do_nothing:
          }
      }
    \__tblr_seq_set_split_aux:Nw #1 \prg_do_nothing: #3 #2 \c_novalue_tl #2
  }
\cs_generate_variant:Nn \__tblr_seq_set_split:Nnn { NnV }

%% Split tl #3 into items separated by tl #2, and store the result in seq #1.
%% Spaces on both side of items and outer braces around items are kept.
%% And we prevent splitting inside the body of an environment.
\cs_new_protected:Npn \__tblr_seq_set_split_keep_braces_envs:Nnn #1 #2 #3
  {
    \__tblr_seq_set_split_keep_envs_aux:NnnN
      #1 { #2 }  { #3 } \__tblr_seq_set_split:Nnn
  }
\cs_generate_variant:Nn \__tblr_seq_set_split_keep_braces_envs:Nnn { NnV }

%% Split tl #3 into items separated by tl #2, and store the result in seq #1.
%% Spaces on both side of items and outer braces around items are removed.
%% And we prevent splitting inside the body of an environment.
\cs_new_protected:Npn \__tblr_seq_set_split_keep_envs:Nnn #1 #2 #3
  {
    \__tblr_seq_set_split_keep_envs_aux:NnnN
      #1 { #2 }  { #3 } \seq_set_split:Nnn
  }
\cs_generate_variant:Nn \__tblr_seq_set_split_keep_envs:Nnn { NnV }

\seq_new:N \l__tblr_split_raw_seq
\seq_new:N \l__tblr_split_temp_seq
\seq_new:N \l__tblr_split_item_seq
\int_new:N \l__tblr_split_balance_int

\cs_new_protected:Npn \__tblr_seq_set_split_keep_envs_aux:NnnN #1 #2 #3 #4
  {
    #4 \l__tblr_split_raw_seq { #2 }  { #3 }
    \seq_clear:N #1
    \seq_clear:N \l__tblr_split_item_seq
    \seq_map_inline:Nn \l__tblr_split_raw_seq
      {
        \seq_put_right:Nn \l__tblr_split_item_seq { ##1 }
        \seq_set_split:Nnn \l__tblr_split_temp_seq { \begin } { ##1 }
        \int_add:Nn \l__tblr_split_balance_int
          { \seq_count:N \l__tblr_split_temp_seq }
        \seq_set_split:Nnn \l__tblr_split_temp_seq { \end } { ##1 }
        \int_sub:Nn \l__tblr_split_balance_int
          { \seq_count:N \l__tblr_split_temp_seq }
        \int_compare:nNnT { \l__tblr_split_balance_int } = { 0 }
          {
            \seq_put_right:Ne #1 { \seq_use:Nn \l__tblr_split_item_seq { #2 } }
            \seq_clear:N \l__tblr_split_item_seq
          }
      }
  }

%% Split table content to a sequence of lines
%% #1: tl with table contents, #2: resulting sequence of lines
\cs_new_protected:Npn \__tblr_split_table_to_lines:NN #1 #2
  {
    \__tblr_seq_set_split_keep_braces_envs:NnV \l_tmpa_seq { \\ } #1
    \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 { \hborder { pagebreak = no } }
            \tl_set:Ne \l__tblr_c_tl { \tl_tail:n {##1} }
            \tl_trim_spaces:N \l__tblr_c_tl %% Ignore spaces between * and [dimen]
            \tl_if_head_eq_meaning:VNT \l__tblr_c_tl [
              {
                \tl_put_right:Nn \l__tblr_b_tl { \RowBefore@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@AddBelowSep ##1 } }
              { \seq_put_right:Nn #2 { ##1 } }
          }
      }
    \int_set:Nn \c@rowcount { \seq_count:N #2 }
  }

%% Treat \\[dimen] command
\NewTblrTableCommand \RowBefore@AddBelowSep [1] []
  {
    \IfValueT { #1 }
      {
        \__tblr_data_gadd_dimen_value:nene { row }
          { \int_eval:n {\c@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
  {
    \__tblr_seq_set_split_keep_braces_envs:Nnn \l_tmpa_seq { & } { #2 }
    \int_set:Nn \c@rownum {#1}
    \int_zero:N \c@colnum
    \seq_map_inline:Nn \l_tmpa_seq
      {
        \tl_set:Nn \l_tmpa_tl { ##1 }
        \__tblr_trim_par_space_tokens_left:N \l_tmpa_tl
        \int_incr:N \c@colnum
        \__tblr_extract_table_commands:N \l_tmpa_tl
        \__tblr_trim_par_space_tokens:N \l_tmpa_tl
        \__tblr_spec_gput:neV { text } { [#1][\int_use:N \c@colnum] } \l_tmpa_tl
      }
    %% 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_all:nTF
      {
        { \int_compare_p:nNn {#1} = {\c@rowcount} }
        { \int_compare_p:nNn {\c@colnum} = {1} }
        { \tl_if_empty_p:N \l_tmpa_tl }
      }
      { \int_decr:N \c@rowcount }
      {
        \__tblr_prop_gput:nne
          {row} { [#1] / cell-number } { \int_use:N \c@colnum }
        \int_compare:nT { \c@colnum > \c@colcount }
          {
            \int_set_eq:NN \c@colcount \c@colnum
          }
      }
  }

\cs_new_protected:Npn \__tblr_trim_par_space_tokens_left:N #1
  {
    \tl_trim_spaces:N #1
    \__tblr_remove_head_par:N #1
    \tl_trim_spaces:N #1
  }

\tl_new:N \l__tblr_trim_temp_tl

\cs_new_protected:Npn \__tblr_trim_par_space_tokens:N #1
  {
    \tl_trim_spaces:N #1
    \__tblr_remove_head_par:N #1
    \tl_set_eq:NN \l__tblr_trim_temp_tl #1
    \tl_reverse:N \l__tblr_trim_temp_tl
    \__tblr_remove_head_par:N \l__tblr_trim_temp_tl
    \tl_reverse:N \l__tblr_trim_temp_tl
    \tl_set_eq:NN #1 \l__tblr_trim_temp_tl
    \tl_trim_spaces:N #1
  }

\cs_new_protected:Npn \__tblr_remove_head_par:N #1
  {
    \tl_if_head_eq_meaning:VNT #1 \par { \tl_set:Ne #1 { \tl_tail:N #1 } }
  }

%%% --------------------------------------------------------
%%> \section{Extract table commands from cell text}
%%% --------------------------------------------------------

%% Extract table commands defined with \NewTblrTableCommand from cell text

\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
  {
    % We need to execute \SetChild commands before parsing inner specs,
    % but execute other table commands after parsing inner specs.
    \__tblr_child_extract_index_command: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
    \exp_last_unbraced:NV \__tblr_extract_table_commands_next: #1 \q_stop
    \tl_if_empty:NF \l__tblr_saved_table_commands_before_cell_text_tl
      {
        \__tblr_prop_gput:neV { command }
          {[\int_use:N \c@rownum][\int_use:N \c@colnum]}
          \l__tblr_saved_table_commands_before_cell_text_tl
      }
    \tl_set_eq:NN #1 \l__tblr_saved_cell_text_after_table_commands_tl
  }

\cs_new_protected:Npn \__tblr_extract_table_commands_next:
  {
    \peek_after:Nw \__tblr_extract_table_commands_next_peek:
  }

\cs_new_protected:Npn \__tblr_extract_table_commands_next_peek:
  {
    \token_if_group_begin:NTF \l_peek_token
      {
        \__tblr_save_real_cell_text:
      }
      {
        \__tblr_extract_table_commands_next_real:n
      }
  }

%% #1 maybe a single token or multiple tokens from a pair of braces
\cs_new_protected:Npn \__tblr_extract_table_commands_next_real:n #1
  {
    \tl_if_single_token:nTF {#1}
      {
        \clist_if_in:NnTF \g__tblr_table_commands_clist { #1 }
          { \__tblr_extract_one_table_command:N #1 }
          {
            \token_if_eq_meaning:NNF #1 \q_stop
              { \__tblr_save_real_cell_text: #1 }
          }
      }
      { \__tblr_save_real_cell_text: {#1} }
  }

\cs_new_protected:Npn \__tblr_extract_one_table_command:N #1
  {
    \int_set:Nn \l__tblr_a_int
      { \tl_use:c { g__tblr_table_cmd_ \cs_to_str:N #1 _arg_numb_tl } }
    \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
      }
      { \__tblr_extract_table_commands_next: }
  }

%% We prepend \prg_do_nothing: to avoid losing outermost braces.
\cs_new_protected:Npn \__tblr_save_real_cell_text:
  {
    \__tblr_save_real_cell_text_aux:w \prg_do_nothing:
  }

\cs_new_protected:Npn \__tblr_save_real_cell_text_aux:w #1 \q_stop
  {
    \tl_set:No \l__tblr_saved_cell_text_after_table_commands_tl {#1}
  }

%%% --------------------------------------------------------
%%> \section{Initialize table inner specifications}
%%% --------------------------------------------------------

\prop_new:N \g__tblr_initial_table_prop
\prop_new:N \g__tblr_initial_rows_prop
\prop_new:N \g__tblr_initial_columns_prop
\prop_new:N \g__tblr_initial_cells_prop
\prop_new:N \g__tblr_initial_hlines_prop
\prop_new:N \g__tblr_initial_vlines_prop

\prop_gset_from_keyval:Nn \g__tblr_initial_table_prop
  {
    stretch = 1,
    rulesep = 2pt,
  }

\prop_gset_from_keyval:Nn \g__tblr_initial_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_initial_columns_prop
  {
    leftsep = 6pt,
    rightsep = 6pt,
    width = -1pt, % column width unset
    coefficient = 0, % column coefficient unset
    @col-width = 0pt,
  }

\prop_gset_from_keyval:Nn \g__tblr_initial_cells_prop
  {
    halign = j,
    valign = t,
    width = -1pt, % cell width unset
    rowspan = 1,
    colspan = 1,
    omit = 0,
  }

\prop_gset_from_keyval:Nn \g__tblr_initial_hlines_prop
  {
    @hline-count = 0,
  }

\prop_gset_from_keyval:Nn \g__tblr_initial_vlines_prop
  {
    @vline-count = 0,
  }

\tl_new:N \l__tblr_inner_spec_measure_tl
\tl_set:Nn \l__tblr_inner_spec_measure_tl { hbox }

\cs_new_protected:Npn \__tblr_init_table_inner_spec:
  {
    \prop_map_inline:Nn \g__tblr_initial_table_prop
      {
        \__tblr_prop_gput:nen { inner } { ##1 } {##2}
      }
    \int_step_variable:nNn { \c@rowcount } \l__tblr_i_tl
      {
        \prop_map_inline:Nn \g__tblr_initial_rows_prop
          {
            \__tblr_data_gput:nVnn { row } \l__tblr_i_tl {##1} {##2}
          }
        \prop_map_inline:Nn \g__tblr_initial_hlines_prop
          {
            \__tblr_spec_gput:nen { hline } { [\l__tblr_i_tl] / ##1 } {##2}
          }
        \int_step_variable:nNn { \c@colcount } \l__tblr_j_tl
          {
            \prop_map_inline:Nn \g__tblr_initial_cells_prop
              {
                \__tblr_data_gput:neeen { cell }
                  { \l__tblr_i_tl } { \l__tblr_j_tl } {##1} {##2}
              }
          }
      }
    \prop_map_inline:Nn \g__tblr_initial_hlines_prop
      {
        \__tblr_spec_gput:nen { hline }
          { [\int_eval:n { \c@rowcount + 1}] / ##1 } {##2}
      }
    \int_step_variable:nNn { \c@colcount } \l__tblr_j_tl
      {
        \prop_map_inline:Nn \g__tblr_initial_columns_prop
          {
            \__tblr_data_gput:nenn { column } { \l__tblr_j_tl } {##1} {##2}
          }
        \prop_map_inline:Nn \g__tblr_initial_vlines_prop
          {
            \__tblr_spec_gput:nen { vline } { [\l__tblr_j_tl] / ##1 } {##2}
          }
      }
    \prop_map_inline:Nn \g__tblr_initial_vlines_prop
      {
        \__tblr_spec_gput:nen { vline }
          { [\int_eval:n { \c@colcount + 1}] / ##1 } {##2}
      }
    \__tblr_keys_set:nv { table/inner }
      { l__tblr_default_ \l__tblr_env_name_tl _inner_tl }
  }

%%% --------------------------------------------------------
%%> \section{Parse table inner specifications}
%%% --------------------------------------------------------

%% We must define these keys to make \__tblr_keys_if_exist work
\__tblr_keys_define:nn { table/inner }
  {
    column  .code:n = ,
    row     .code:n = ,
    cell    .code:n = ,
    hline   .code:n = ,
    vline   .code:n = ,
    hborder .code:n = ,
    vborder .code:n =
  }

\str_new:N \g__tblr_name_str

\__tblr_keys_define:nn { table/inner }
  {
    name .code:n = \__tblr_keys_gput:nn { name } {#1},
    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:ne { width } { \dim_eval:n {#1} },
    hspan .code:n = \__tblr_keys_gput:nn { hspan } {#1},
    vspan .code:n = \__tblr_keys_gput:nn { vspan } {#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 },
    rulesep .code:n = \__tblr_keys_gput:nn { rulesep } {#1},
    rowhead .code:n = \__tblr_keys_gput:nn { rowhead } {#1},
    rowfoot .code:n = \__tblr_keys_gput:nn { rowfoot } {#1},
    delimiter .code:n = \__tblr_set_delimiter:n {#1},
    baseline .code:n = \__tblr_outer_gput_spec:nn { baseline } {#1},
    unknown .code:n = \__tblr_table_special_key:Vn \l_keys_key_str {#1},
  }

\msg_new:nnn { tabularray } { unknown-inner-key }
  { Unknown ~ inner ~ key ~ name ~ '#1'. }

\cs_new_protected:Npn \__tblr_table_special_key:nn #1 #2
  {
    \__tblr_key_split_name_args:n {#1}
    \__tblr_keys_if_exist:nVTF { table/inner } \l__tblr_key_split_name_str
      {
        \use:c { __tblr_set_ \l__tblr_key_split_name_str _aux:Vn }
          \l__tblr_key_split_args_tl {#2}
      }
      {
        \msg_error:nnV { tabularray } { unknown-inner-key }
          \l__tblr_key_split_name_str
      }
  }
\cs_generate_variant:Nn \__tblr_table_special_key:nn { Vn }

%% If the first key name is known, treat #1 as table spec;
%% otherwise, treat #1 as colspec.

\cs_new_protected:Npn \__tblr_parse_table_spec:n #1
  {
    \__tblr_keyval_extract_first_name:n {#1}
    \__tblr_keys_if_exist:nVTF { table/inner } \l__tblr_key_split_name_str
      { \__tblr_keys_set:nn { table/inner } {#1} }
      { \__tblr_parse_colrow_spec:nn { Column } {#1} }
    \str_gset:Ne \g__tblr_name_str { \__tblr_prop_item:ne { inner } { name } }
    \str_if_empty:NT \g__tblr_name_str
      { \str_gset:Ne \g__tblr_name_str { @ \int_use:N \c@tblrcount } }
  }

\cs_new_protected:Npn \__tblr_keys_gput:nn #1 #2
  {
    \__tblr_prop_gput:nnn { inner } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_keys_gput:nn { ne }

\__tblr_keys_define:nn { table/delimiter }
  {
    left .code:n  = \__tblr_keys_gput:nn { delim-left } { \left #1 },
    right .code:n = \__tblr_keys_gput:nn { delim-right } { \right #1 }
  }

\cs_new_protected:Npn \__tblr_set_delimiter:n #1
  {
    \__tblr_keys_set:nn { table/delimiter } {#1}
  }

%%% --------------------------------------------------------
%%> \section{Initialize and parse table outer specifications}
%%% --------------------------------------------------------

\msg_new:nnn { tabularray } { used-theme-name }
  { theme ~ name ~ #1 ~ has ~ been ~ used! }

%% #1: theme names; #2: template and style commands
\NewDocumentCommand \NewTblrTheme { m +m }
  {
    \tl_if_exist:cTF { g__tblr_theme_ #1 _code_tl }
      { \msg_error:nnn { tabularray } { used-theme-name } { #1 } }
      {
        \tl_set:cn { g__tblr_theme_ #1 _code_tl } {#2}
        \ignorespaces
      }
  }

\cs_new_protected:Npn \__tblr_use_theme:n #1
  {
    \ignorespaces
    \tl_use:c { g__tblr_theme_ #1 _code_tl }
  }

\cs_new_protected:Npn \__tblr_init_table_outer_spec:
  {
    \__tblr_keys_set:nv { table/outer }
      { l__tblr_default_ \l__tblr_env_name_tl _outer_tl }
  }

\cs_new_protected:Npn \__tblr_parse_table_option:n #1
  {
    \__tblr_keys_set:nn { table/outer } {#1}
  }

%% We must define these keys to make \__tblr_keys_if_exist work
\__tblr_keys_define:nn { table/outer }
  {
    note   .code:n = ,
    remark .code:n = ,
    more   .code:n =
  }

\__tblr_keys_define:nn { table/outer }
  {
    long    .code:n = \__tblr_outer_gput_spec:nn { portrait } { long },
    tall    .code:n = \__tblr_outer_gput_spec:nn { portrait } { tall },
    halign  .code:n = \__tblr_outer_gput_spec:nn { halign } {#1},
    baseline .code:n = \__tblr_outer_gput_spec:nn { baseline } {#1},
    l       .meta:n = { halign = l },
    c       .meta:n = { halign = c },
    r       .meta:n = { halign = r },
    t       .meta:n = { baseline = t },
    T       .meta:n = { baseline = T },
    m       .meta:n = { baseline = m },
    M       .meta:n = { baseline = M },
    b       .meta:n = { baseline = b },
    B       .meta:n = { baseline = B },
    valign  .meta:n = { baseline = #1 }, % obsolete, will be removed some day
    expand  .code:n = \__tblr_outer_gput_spec:nn { expand } {#1},
    expand+ .code:n = \__tblr_outer_gconcat_spec:nn { expand } {#1},
    headsep .code:n = \__tblr_outer_gput_spec:nn { headsep } {#1},
    footsep .code:n = \__tblr_outer_gput_spec:nn { footsep } {#1},
    presep  .code:n = \__tblr_outer_gput_spec:nn { presep }  {#1},
    postsep .code:n = \__tblr_outer_gput_spec:nn { postsep } {#1},
    theme   .code:n = \__tblr_use_theme:n {#1},
    caption .code:n = \__tblr_outer_gput_spec:nn { caption } {#1},
    entry   .code:n = \__tblr_outer_gput_spec:nn { entry } {#1},
    label   .code:n = \__tblr_outer_gput_spec:nn { label } {#1},
    unknown .code:n = \__tblr_table_option_key:Vn \l_keys_key_str {#1},
  }

\cs_new_protected:Npn \__tblr_outer_gput_spec:nn #1 #2
  {
    \__tblr_spec_gput:nen { outer } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_outer_gput_spec:nn { ne }

\cs_new_protected:Npn \__tblr_outer_gconcat_spec:nn #1 #2
  {
    \__tblr_outer_gput_spec:ne
      {#1} { \__tblr_spec_item:nn { outer } { #1 } \exp_not:n { #2 } }
  }

\msg_new:nnn { tabularray } { unknown-outer-key }
  { Unknown ~ outer ~ key ~ name ~ '#1'. }

\cs_new_protected:Npn \__tblr_table_option_key:nn #1 #2
  {
    \__tblr_key_split_name_args:n {#1}
    \__tblr_keys_if_exist:nVTF { table/outer } \l__tblr_key_split_name_str
      {
        % remove a pair of outer braces
        \tl_set:Ne \l__tblr_key_split_args_tl
          { \tl_head:N \l__tblr_key_split_args_tl }
        \use:c { __tblr_outer_gput_ \l__tblr_key_split_name_str :Vn }
          \l__tblr_key_split_args_tl {#2}
      }
      {
        \msg_error:nnV { tabularray } { unknown-outer-key }
          \l__tblr_key_split_name_str
      }
  }
\cs_generate_variant:Nn \__tblr_table_option_key:nn { Vn }

\cs_new_protected:Npn \__tblr_outer_gput_note:nn #1 #2
  {
    \__tblr_prop_gput:nnn { note } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_outer_gput_note:nn { Vn }

\cs_new_protected:Npn \__tblr_outer_gput_remark:nn #1 #2
  {
    \__tblr_prop_gput:nnn { remark } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_outer_gput_remark:nn { Vn }

\cs_new_protected:Npn \__tblr_outer_gput_more:nn #1 #2
  {
    \__tblr_prop_gput:nnn { more } {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_outer_gput_more:nn { Vn }

%%% --------------------------------------------------------
%%> \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_prepare_stretch:
    \__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:
  }

%% prepare stretch option of the table
\fp_new:N \l__tblr_stretch_fp
\dim_new:N \l__tblr_strut_dp_dim
\dim_new:N \l__tblr_strut_ht_dim
\cs_new_protected:Npn \__tblr_prepare_stretch:
  {
    \fp_set:Nn \l__tblr_stretch_fp
      { \__tblr_prop_item:nn { inner } { stretch } }
    \fp_compare:nNnTF \l__tblr_stretch_fp > \c_zero_fp
      {
        \dim_set:Nn \l__tblr_strut_dp_dim
          { \fp_use:N \l__tblr_stretch_fp \box_dp:N \strutbox }
        \dim_set:Nn \l__tblr_strut_ht_dim
          { \fp_use:N \l__tblr_stretch_fp \box_ht:N \strutbox }
        \cs_set_eq:NN \__tblr_leave_vmode: \mode_leave_vertical:
        \cs_set_eq:NN \__tblr_process_stretch: \__tblr_process_stretch_real:
      }
      {
        \cs_set_eq:NN \__tblr_process_stretch: \prg_do_nothing:
        \fp_compare:nNnTF \l__tblr_stretch_fp < \c_zero_fp
          {
            % for lists (see issue #99)
            \cs_set_eq:NN \__tblr_leave_vmode: \@setminipage
          }
          { \cs_set_eq:NN \__tblr_leave_vmode: \mode_leave_vertical: }
      }
  }
\cs_new_eq:NN \__tblr_leave_vmode: \mode_leave_vertical:
\cs_new_protected:Npn \__tblr_process_stretch_real:
  {
    \dim_compare:nNnT \l__tblr_strut_dp_dim > { \box_dp:N \l_tmpb_box }
      {
        \box_set_dp:Nn \l_tmpa_box
          {
              \box_dp:N \l_tmpa_box
            - \box_dp:N \l_tmpb_box
            + \l__tblr_strut_dp_dim
          }
        \box_set_dp:Nn \l_tmpb_box { \l__tblr_strut_dp_dim }
      }
    \dim_compare:nNnT \l__tblr_strut_ht_dim > { \box_ht:N \l_tmpa_box }
      {
        \hbox_set:Nn \l_tmpa_box { \box_use:N \l_tmpa_box }
        \hbox_set:Nn \l_tmpb_box { \box_use:N \l_tmpb_box }
        \box_set_ht:Nn \l_tmpb_box
          {
              \box_ht:N \l_tmpb_box
            - \box_ht:N \l_tmpa_box
            + \l__tblr_strut_ht_dim
          }
        \box_set_ht:Nn \l_tmpa_box { \l__tblr_strut_ht_dim }
        %% return vbox for vertical-align: \c__tblr_middle_m_tl
        \vbox_set_top:Nn \l_tmpa_box { \box_use:N \l_tmpa_box }
        \vbox_set:Nn \l_tmpb_box { \box_use:N \l_tmpb_box }
      }
  }
\cs_new_eq:NN \__tblr_process_stretch: \__tblr_process_stretch_real:

%% 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@rownum
    \int_zero:N \c@colnum
    \int_step_inline:nn { \c@rowcount + 1 }
      {
        \int_incr:N \c@rownum
        \int_zero:N \c@colnum
        \int_step_inline:nn { \c@colcount + 1 }
          {
            \int_incr:N \c@colnum
            \int_compare:nNnT { ##1 } < { \c@rowcount + 1 }
              {
                \__tblr_measure_and_update_vline_size:nn { ##1 } { ####1 }
              }
            \int_compare:nNnT { ####1 } < { \c@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:Ne \l__tblr_n_tl
      { \__tblr_spec_item:ne { vline } { [#2] / @vline-count } }
    \int_compare:nNnT { \l__tblr_n_tl } > {0}
      {
        \tl_set:Ne \l__tblr_s_tl
          { \__tblr_prop_item:ne { inner } { 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:Ne \l__tblr_w_tl { \dim_eval:n { \box_wd:N \l__tblr_b_box } }
            \__tblr_spec_gput_if_larger:nee { vline }
              { [#2](##1) / @vline-width } { \l__tblr_w_tl }
            \dim_add:Nn \l__tblr_w_dim
              {
                \__tblr_spec_item:nn  { vline } { [#2](##1) / @vline-width }
              }
            \dim_add:Nn \l__tblr_w_dim { \l__tblr_s_tl }
          }
        \dim_add:Nn \l__tblr_w_dim { - \l__tblr_s_tl }
      }
    \__tblr_spec_gput_if_larger:nee { 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:Ne \l__tblr_w_tl
      { \__tblr_spec_item:ne { vline } { [#1][#2](#3) / wd } }
    \tl_if_empty:NF \l__tblr_w_tl
      { \dim_set:Nn \lTblrDefaultVruleWidthDim { \l__tblr_w_tl } }
    \tl_set:Ne \l__tblr_d_tl
      { \__tblr_spec_item:ne { vline } { [#1][#2](#3) / @dash } }
    \tl_set:Ne \l__tblr_b_tl { \tl_tail:N \l__tblr_d_tl }
    \tl_if_head_eq_meaning:VNTF \l__tblr_d_tl \q__tblr_dash
      {
        \__tblr_get_vline_dash_style:N \l__tblr_b_tl
        \xleaders \l__tblr_b_tl \vfil
      }
      {
        %% When using text as vline, we need to omit abovepos and belowpos.
        \unskip
        \hbox_set:Nn \l__tblr_d_box
          {
            \bool_if:NTF \l__tblr_math_mode_bool
              { $ \l__tblr_b_tl $ } { \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
        \vss
      }
    \group_end:
  }
\cs_generate_variant:Nn \__tblr_get_vline_segment_child:nnnnn { nnnee }

%% 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:Ne \l__tblr_n_tl
      { \__tblr_spec_item:ne { hline } { [#1] / @hline-count } }
    \int_compare:nNnT { \l__tblr_n_tl } > {0}
      {
        \tl_set:Ne \l__tblr_s_tl
          { \__tblr_prop_item:ne { inner } { 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:Ne \l__tblr_h_tl
              {
                \dim_eval:n
                  { \box_ht:N \l__tblr_b_box + \box_dp:N \l__tblr_b_box }
              }
            \__tblr_spec_gput_if_larger:nee { hline }
              { [#1](##1) / @hline-height } { \l__tblr_h_tl }
            \dim_add:Nn \l__tblr_h_dim
              {
                \__tblr_spec_item:nn  { hline } { [#1](##1) / @hline-height }
              }
            \dim_add:Nn \l__tblr_h_dim { \l__tblr_s_tl }
          }
        \dim_add:Nn \l__tblr_h_dim { - \l__tblr_s_tl }
      }
    \__tblr_spec_gput_if_larger:nee { 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:Ne \l__tblr_w_tl
      { \__tblr_spec_item:ne { hline } { [#1][#2](#3) / wd } }
    \tl_if_empty:NF \l__tblr_w_tl
      { \dim_set:Nn \lTblrDefaultHruleWidthDim { \l__tblr_w_tl } }
    \tl_set:Ne \l__tblr_d_tl
      { \__tblr_spec_item:ne { hline } { [#1][#2](#3) / @dash } }
    \tl_set:Ne \l__tblr_a_tl { \tl_head:N \l__tblr_d_tl }
    \tl_set:Ne \l__tblr_b_tl { \tl_tail:N \l__tblr_d_tl }
    \tl_if_head_eq_meaning:VNTF \l__tblr_d_tl \q__tblr_dash
      {
        \__tblr_get_hline_dash_style:N \l__tblr_b_tl
        \xleaders \l__tblr_b_tl \hfil
      }
      {
        \bool_if:NTF \l__tblr_math_mode_bool
          { $ \l__tblr_b_tl $ } { \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:Ne \g__tblr_cell_halign_tl
      { \__tblr_data_item:neen { cell } {#1} {#2} { halign } }
    \tl_set:Ne \l__tblr_v_tl
      { \__tblr_data_item:neen { 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@rownum
    \int_zero:N \c@colnum
    \__tblr_save_counters:n { table }
    \int_step_inline:nn { \c@rowcount }
      {
        \int_incr:N \c@rownum
        \int_zero:N \c@colnum
        \__tblr_update_rowsep_registers:
        %% Note that \__tblr_data_item always returns nonempty value,
        %% but \__tblr_prop_item may return empty value.
        %% To make \__tblr_map_data_to_prop: work, we need to add + 0pt.
        \tl_set:Ne \l__tblr_h_tl
          {
            \__tblr_data_item:nen { row } { \int_use:N \c@rownum } { height }
            + 0pt
          }
        %% We didn't initialize row heights with -1pt
        \dim_compare:nNnF { \l__tblr_h_tl } = { 0pt }
          {
            \__tblr_data_gput:nenV { row } { \int_use:N \c@rownum }
              { @row-height } \l__tblr_h_tl
          }
        \int_step_inline:nn { \c@colcount }
          {
            \int_incr:N \c@colnum
            \__tblr_update_colsep_registers:
            \__tblr_measure_cell_update_sizes:nnNNNN
              { \int_use:N \c@rownum }
              { \int_use:N \c@colnum }
              \g__tblr_cell_wd_dim
              \g__tblr_cell_ht_dim
              \g__tblr_cell_head_dim
              \g__tblr_cell_foot_dim
          }
      }
    \__tblr_restore_counters:n { table }
    \int_step_inline:nn { \c@colcount }
      {
        \tl_set:Ne \l__tblr_w_tl
          { \__tblr_data_item:nen { column } {##1} { width } }
        \dim_compare:nNnF { \l__tblr_w_tl } < { 0pt }
          {
            \__tblr_data_gput:nenV { column } {##1} { @col-width } \l__tblr_w_tl
          }
      }
  }

\cs_new_protected:Npn \__tblr_update_rowsep_registers:
  {
    \dim_set:Nn \abovesep
      { \__tblr_data_item:nen { row } { \int_use:N \c@rownum } { abovesep } }
    \dim_set:Nn \belowsep
      { \__tblr_data_item:nen { row } { \int_use:N \c@rownum } { belowsep } }
  }

\cs_new_protected:Npn \__tblr_update_colsep_registers:
  {
    \dim_set:Nn \leftsep
      { \__tblr_data_item:nen { column } { \int_use:N \c@colnum } { leftsep } }
    \dim_set:Nn \rightsep
      { \__tblr_data_item:nen { column } { \int_use:N \c@colnum } { rightsep } }
  }

%% 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
  {
    \int_compare:nNnTF { \__tblr_data_item:neen { cell } {#1} {#2} { omit } } > {0}
      {
        \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 } }
  }

\tl_new:N \l__tblr_cell_fg_tl
\tl_new:N \l__tblr_cell_cmd_tl
\tl_new:N \l__tblr_cell_mode_tl
\bool_new:N \l__tblr_cell_math_mode_bool
\tl_const:Nn \c__tblr_cell_math_style_tl { \relax }
\tl_const:Nn \c__tblr_cell_imath_style_tl { \textstyle }
\tl_const:Nn \c__tblr_cell_dmath_style_tl { \displaystyle }
\box_new:N \l__tblr_measured_cell_box

%% 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:Ne \l__tblr_c_tl { \__tblr_spec_item:ne { text } {[#1][#2]} }
    %% when the cell text is guarded by a pair of curly braces,
    %% we unbrace it and ignore cmd option of the cell, see issue #90.
    \bool_lazy_and:nnTF
      { \tl_if_single_p:N \l__tblr_c_tl }
      { \exp_args:NV \tl_if_head_is_group_p:n \l__tblr_c_tl }
      { \exp_last_unbraced:NNV \tl_set:Nn \l__tblr_c_tl \l__tblr_c_tl }
      {
        \tl_set:Ne \l__tblr_cell_cmd_tl
          { \__tblr_data_item:neen { cell } {#1} {#2} { cmd } }
        \tl_if_empty:NF \l__tblr_cell_cmd_tl
          {
            \tl_set:Ne \l__tblr_c_tl
              { \exp_not:V \l__tblr_cell_cmd_tl { \exp_not:V \l__tblr_c_tl } }
          }
      }
    \tl_set:Ne \l__tblr_cell_mode_tl
      { \__tblr_data_item:neen { cell } {#1} {#2} { mode } }
    \tl_if_empty:NT \l__tblr_cell_mode_tl
      {
        \bool_if:NTF \l__tblr_math_mode_bool
          { \tl_set:Nn \l__tblr_cell_mode_tl { math } }
          { \tl_set:Nn \l__tblr_cell_mode_tl { text } }
      }
    \tl_if_eq:NnTF \l__tblr_cell_mode_tl { text }
      { \bool_set_false:N \l__tblr_cell_math_mode_bool }
      {
        \bool_set_true:N \l__tblr_cell_math_mode_bool
        \tl_put_left:Nv \l__tblr_c_tl
          { c__tblr_cell_ \l__tblr_cell_mode_tl _style_tl }
        \tl_put_left:Nn \l__tblr_c_tl { $ }
        \tl_put_right:Nn \l__tblr_c_tl { $ }
      }
    \tl_set:Ne \l__tblr_f_tl { \__tblr_data_item:neen { cell } {#1} {#2} { font } }
    \tl_set:Ne \l__tblr_w_tl
      { \__tblr_data_item:neen { cell } {#1} {#2} { width } }
    \dim_compare:nNnT { \l__tblr_w_tl } < { 0pt } % cell width unset
      {
        \int_compare:nNnT
          { \__tblr_data_item:neen { cell } {#1} {#2} { colspan } } < {2}
          {
            \tl_set:Ne \l__tblr_w_tl
              { \__tblr_data_item:nen { column } {#2} { width } }
          }
      }
    \box_clear:N \l__tblr_measured_cell_box
    \dim_compare:nNnT { \l__tblr_w_tl } < { 0pt } % column width unset
      {
        %% To keep \lTblrMeasuringBool correct we need measure=vstore from varwidth.
        %% A temporary private hook for testfiles/supporthook/regression-test.cfg.
        \use:c { __tblr_temp_hook_for_measure_vstore_testing: }
        \tl_if_eq:NnTF \l__tblr_inner_spec_measure_tl { vstore }
          {
            \__tblr_build_vcell_with_vstore:
          }
          {
            \__tblr_save_counters:n { cell }
            \bool_if:NTF \l__tblr_cell_math_mode_bool
              {
                %% Note that font = \boldmath will increase cell width (issue #137)
                \hbox_set:Nn \l_tmpa_box { \l__tblr_f_tl \l__tblr_c_tl }
                \tl_set:Ne \l__tblr_w_tl { \dim_eval:n { \box_wd:N \l_tmpa_box } }
              }
              {
                \__tblr_get_cell_size_with_box:
              }
            \__tblr_restore_counters:n { cell }
          }
      }
    \tl_put_left:NV \l__tblr_c_tl \l__tblr_f_tl
    \tl_set:Ne \l__tblr_cell_fg_tl
      { \__tblr_data_item:neen { cell } {#1} {#2} { foreground } }
    \tl_if_empty:NF \l__tblr_cell_fg_tl
      { \exp_args:NV \color \l__tblr_cell_fg_tl }
    \__tblr_get_vcell_and_sizes:NN \l__tblr_c_tl \l__tblr_w_tl
    \group_end:
  }

\box_new:N \g__tblr_last_box
\box_new:N \l__tblr_temp_box

%% Build cell vbox with varwidth and use it later when hook library is loaded
%% We apply \lastbox trick to get the cell vbox with correct width
\cs_new_protected:Npn  \__tblr_build_vcell_with_vstore:
  {
    \vbox_set:Nn \l__tblr_temp_box % we don't use it since its width is too large
      {
        \begin{varwidth}[t]{\paperwidth}
          \TblrParboxRestore
          \cs:w __tblr_halign_command_ \g__tblr_cell_halign_tl : \cs_end:
          \__tblr_leave_vmode:
          \l__tblr_f_tl
          \l__tblr_c_tl
        \end{varwidth}
        \box_gset_to_last:N \g__tblr_last_box
      }
    \box_set_eq:NN \l__tblr_measured_cell_box \g__tblr_last_box
    \tl_set:Ne \l__tblr_w_tl
      { \dim_eval:n { \box_wd:N \l__tblr_measured_cell_box } }
  }

%% Measure cell width only and build it later when hook library is not loaded
\cs_new_protected:Npn \__tblr_get_cell_size_with_box:
  {
    \tl_if_eq:NnTF \l__tblr_inner_spec_measure_tl { vbox }
      { \__tblr_get_cell_size_with_vbox: }
      { \__tblr_get_cell_size_with_hbox: }
  }

%% Varwidth won't work as expected when \color command occurs in it,
%% and we can not fix this problem with \leavevmode command.
%% See https://tex.stackexchange.com/q/460489.
%% But we need to use \color command for fg option,
%% or users may use it in the middle of the cell text,
%% so we have redefine \color command and disable it before measuring cell.

%% In order to correctly measure an enumerate environment,
%% we need to enclose varwidth with NoHyper environment (see issue #196).

\NewDocumentCommand \__tblr_fake_color_command:w { o m } { }

\cs_new_protected:Npn \__tblr_get_cell_size_with_vbox:
  {
    \hbox_set:Nn \l_tmpa_box
      {
        \cs_set_eq:NN \color \__tblr_fake_color_command:w
        \begin{tblrNoHyper}
        \begin{varwidth}{\paperwidth}
           \l__tblr_f_tl
           \l__tblr_c_tl
        \end{varwidth}
        \end{tblrNoHyper}
      }
    \tl_set:Ne \l__tblr_w_tl { \dim_eval:n { \box_wd:N \l_tmpa_box } }
  }

\cs_new_protected:Npn \__tblr_get_cell_size_with_hbox:
  {
    \tl_set_eq:NN \l_tmpb_tl \l__tblr_c_tl
    \__tblr_seq_set_split_keep_envs: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
      {
        \hbox_set:Nn \l_tmpa_box
          {
            \l__tblr_f_tl
            \l_tmpa_tl
          }
        \tl_set:Ne \l__tblr_w_tl
          { \dim_max:nn { \l__tblr_w_tl } { \box_wd:N \l_tmpa_box } }
      }
  }

%% #1: cell text; #2: box width
\cs_new_protected:Npn \__tblr_get_vcell_and_sizes:NN #1 #2
  {
    \group_begin:
    \box_if_empty:NTF \l__tblr_measured_cell_box
      { \vbox_set_top:Nn \l_tmpa_box { \__tblr_make_vcell_text:NN #1 #2 } }
      { \box_set_eq:NN \l_tmpa_box \l__tblr_measured_cell_box }
    \vbox_set:Nn \l_tmpb_box { \vbox_unpack:N \l_tmpa_box }
    \__tblr_process_stretch:
    \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:Ne \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:
  }

%% #1: cell text; #2: box width
%% All halign commands are defined at the beginning of the file
\cs_new_protected:Npn \__tblr_make_vcell_text:NN #1 #2
  {
    \dim_set:Nn \tex_hsize:D { #2 }
    \TblrParboxRestore
    \cs:w __tblr_halign_command_ \g__tblr_cell_halign_tl : \cs_end:
    \__tblr_leave_vmode:
    #1
  }

%% #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:Ne #4 { \dim_use:N #2 }
            \tl_set:Ne #5 { \dim_eval:n { #1 - #2 } }
          }
        \c__tblr_middle_m_tl
          {
            \tl_set:Ne \l__tblr_middle_body_tl { \dim_eval:n { #1 - #2 - #3 } }
            \tl_set:Ne #4 { \dim_eval:n { #2 + \l__tblr_middle_body_tl / 2 } }
            \tl_set:Ne #5 { \dim_eval:n { #3 + \l__tblr_middle_body_tl / 2 } }
          }
        \c__tblr_middle_b_tl
          {
            \tl_set:Ne #4 { \dim_eval:n { #1 - #3 } }
            \tl_set:Ne #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:Ne \l__tblr_c_tl
      { \__tblr_data_item:neen { cell } {#1} {#2} { colspan } }
    \int_compare:nNnT { \l__tblr_c_tl } > {1}
      {
        \__tblr_data_gput:neene { cell } {#1} {#2} { @cell-width } {\dim_use:N #3}
        \dim_gzero:N #3 % don't affect column width
      }
    \tl_set:Ne \l__tblr_r_tl
      { \__tblr_data_item:neen { cell } {#1} {#2} { rowspan } }
    \int_compare:nNnT { \l__tblr_r_tl } > {1}
      {
        \tl_case:Nn \g__tblr_cell_valign_tl
          {
            \c__tblr_valign_h_tl
              {
                \tl_set:Ne \l__tblr_u_tl { \dim_use:N #5 }
                \tl_set:Ne \l__tblr_v_tl { \dim_eval:n { #4 - #5 } }
                %% Update the head size of the first span row here
                \__tblr_data_gput_if_larger:nene
                  { row } {#1} { @row-head } { \dim_use:N #5 }
              }
            \c__tblr_valign_f_tl
              {
                \tl_set:Ne \l__tblr_u_tl { \dim_eval:n { #4 - #6 } }
                \tl_set:Ne \l__tblr_v_tl { \dim_use:N #6 }
                %% Update the foot size of the last span row here
                \__tblr_data_gput_if_larger:nene
                  { 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_data_gput:neenV { cell } {#1} {#2} { @cell-height } \l__tblr_u_tl
        \__tblr_data_gput:neenV { 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:Ne \l__tblr_h_tl
      { \__tblr_data_item:nen { row } {#1} { @row-height } }
    \tl_if_eq:NNTF \g__tblr_cell_valign_tl \c__tblr_valign_m_tl
      {
        \tl_set:Ne \l__tblr_a_tl
          { \__tblr_data_item:nen { row } {#1} { @row-upper } }
        \tl_set:Ne \l__tblr_b_tl
          { \__tblr_data_item:nen { 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_data_gput:nenV { 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_data_gput:nenV { 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_data_gput:nene { row } {#1} { @row-height }
              { \dim_eval:n { \l__tblr_a_tl + \l__tblr_b_tl } }
          }
      }
      {
        \tl_set:Ne \l__tblr_e_tl
          { \__tblr_data_item:nen { row } {#1} { @row-head } }
        \tl_set:Ne \l__tblr_f_tl
          { \__tblr_data_item:nen { row } {#1} { @row-foot } }
        \dim_compare:nNnT {#4} > {\l__tblr_e_tl}
          {
            \__tblr_data_gput:nene { row } {#1} { @row-head } { \dim_use:N #4 }
          }
        \dim_compare:nNnT {#5} > {\l__tblr_f_tl}
          {
            \__tblr_data_gput:nene { row } {#1} { @row-foot } { \dim_use:N #5 }
          }
        \tl_set:Ne \l__tblr_x_tl { \dim_max:nn {#4} { \l__tblr_e_tl } }
        \tl_set:Ne \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_data_gput:nene { 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:Ne \l_tmpb_tl
      { \__tblr_data_item:nen { 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_data_gput:nene { 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__tblr_column_target_dim
\prop_new:N \l__tblr_column_coefficient_prop
\prop_new:N \l__tblr_column_natural_width_prop
\prop_new:N \l__tblr_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__tblr_column_target_dim } < { 0pt }
      {
        \tl_if_empty:eF { \__tblr_prop_item:nn { inner } { width } }
          {
            \msg_warning:nne { tabularray } { table-width-too-small }
              { \dim_abs:n { \l__tblr_column_target_dim } }
          }
      }
      {
        \prop_if_empty:NF \l__tblr_column_coefficient_prop
          { \__tblr_adjust_extendable_column_width: }
      }
  }

\cs_new_protected:Npn \__tblr_collect_extendable_column_width:
  {
    \tl_set:Ne \l_tmpa_tl { \__tblr_prop_item:nn { inner } { width } }
    \tl_if_empty:NTF \l_tmpa_tl
      { \dim_set_eq:NN \l__tblr_column_target_dim \linewidth }
      { \dim_set:Nn \l__tblr_column_target_dim { \l_tmpa_tl } }
    \prop_clear:N \l__tblr_column_coefficient_prop
    \prop_clear:N \l__tblr_column_natural_width_prop
    \prop_clear:N \l__tblr_column_computed_width_prop
    \int_step_variable:nNn { \c@colcount } \l__tblr_j_tl
      {
        \tl_set:Ne \l__tblr_a_tl
          { \__tblr_data_item:nen { column } { \l__tblr_j_tl } { width } }
        \tl_set:Ne \l__tblr_b_tl
          { \__tblr_data_item:nen { column } { \l__tblr_j_tl } { coefficient } }
        \tl_set:Ne \l__tblr_c_tl
          { \__tblr_data_item:nen  { column } { \l__tblr_j_tl } { @col-width } }
        \dim_compare:nNnTF { \l__tblr_a_tl } < { 0pt } % column width unset
          {
            \dim_compare:nNnTF { \l__tblr_b_tl pt } = { 0pt }
              { \dim_sub:Nn \l__tblr_column_target_dim { \l__tblr_c_tl } }
              {
                \prop_put:Nee \l__tblr_column_coefficient_prop
                  { \l__tblr_j_tl } { \l__tblr_b_tl }
                \prop_put:Nen \l__tblr_column_computed_width_prop
                  { \l__tblr_j_tl } { 0pt }
                \dim_compare:nNnF { \l__tblr_b_tl pt } > { 0pt }
                  {
                    \prop_put:Nee \l__tblr_column_natural_width_prop
                      { \l__tblr_j_tl } { \l__tblr_c_tl }
                  }
              }
          }
          { \dim_sub:Nn \l__tblr_column_target_dim { \l__tblr_a_tl } }
        \tl_set:Ne \l__tblr_a_tl
          { \__tblr_spec_item:ne { vline } { [\l__tblr_j_tl] / @vline-width } }
        \tl_set:Ne \l__tblr_b_tl
          { \__tblr_data_item:nen { column } { \l__tblr_j_tl } { leftsep } }
        \tl_set:Ne \l__tblr_c_tl
          { \__tblr_data_item:nen { column } { \l__tblr_j_tl } { rightsep } }
        \dim_set:Nn \l__tblr_column_target_dim
          {
            \l__tblr_column_target_dim
            - \l__tblr_a_tl - \l__tblr_b_tl - \l__tblr_c_tl
          }
      }
    \tl_set:Ne \l__tblr_a_tl
      {
        \__tblr_spec_item:ne { vline }
          { [\int_eval:n {\c@colcount + 1}] / @vline-width }
      }
    \tl_if_empty:NF \l__tblr_a_tl
      { \dim_sub:Nn \l__tblr_column_target_dim { \l__tblr_a_tl } }
    \LogTblrTracing { target }
  }

%% Users may modify \hfuzz, so we use our hfuzz dim variable (see issue #445)
\dim_new:N \l__tblr_hfuzz_dim
\dim_set:Nn \l__tblr_hfuzz_dim { 0.1pt }

%% If all columns have negative coefficients and small natural widths,
%% \l__tblr_column_coefficient_prop will be empty after one or more rounds.
%% We reset @row-height, etc for \linewidth graphics in X columns (issue #80)
\cs_new_protected:Npn \__tblr_adjust_extendable_column_width:
  {
    \bool_while_do:nn
      { \dim_compare_p:nNn { \l__tblr_column_target_dim } > { \l__tblr_hfuzz_dim } }
      {
        \prop_if_empty:NTF \l__tblr_column_coefficient_prop
          { \__tblr_adjust_extendable_column_width_negative: }
          { \__tblr_adjust_extendable_column_width_once: }
      }
    \prop_map_inline:Nn \l__tblr_column_computed_width_prop
      {
        \__tblr_data_gput:nnne { column } {##1} { width } {##2}
        \__tblr_data_gput:nnnn { column } {##1} { @col-width } { 0pt }
      }
    \int_step_inline:nn { \c@rowcount }
      {
        \__tblr_data_gput:nnnn { row } {##1} { @row-height } { 0pt }
        \__tblr_data_gput:nnnn { row } {##1} { @row-head } { 0pt }
        \__tblr_data_gput:nnnn { row } {##1} { @row-foot } { 0pt }
        \__tblr_data_gput:nnnn { row } {##1} { @row-upper } { 0pt }
        \__tblr_data_gput:nnnn { row } {##1} { @row-lower } { 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__tblr_column_coefficient_prop
      {
        \dim_add:Nn \l_tmpa_dim { \dim_abs:n { ##2 pt } }
      }
    \tl_set:Ne \l__tblr_w_tl
      { \dim_ratio:nn { \l__tblr_column_target_dim } { \l_tmpa_dim } }
    \dim_zero:N \l__tblr_column_target_dim
    \prop_map_inline:Nn \l__tblr_column_coefficient_prop
      {
        \tl_set:Ne \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__tblr_column_computed_width_prop { ##1 } { \l__tblr_a_tl }
          }
          {
            \tl_set:Ne \l__tblr_b_tl
              { \prop_item:Nn \l__tblr_column_natural_width_prop { ##1 } }
            \tl_set:Ne \l__tblr_c_tl
              { \prop_item:Nn \l__tblr_column_computed_width_prop { ##1 } }
            \dim_compare:nNnTF { \l__tblr_a_tl + \l__tblr_c_tl } > { \l__tblr_b_tl }
              {
                \prop_put:Nne \l__tblr_column_computed_width_prop
                  { ##1 } { \l__tblr_b_tl }
                \dim_add:Nn \l__tblr_column_target_dim
                  { \l__tblr_a_tl + \l__tblr_c_tl - \l__tblr_b_tl }
                \prop_remove:Nn \l__tblr_column_coefficient_prop { ##1 }
              }
              {
                \__tblr_add_dimen_value:Nnn
                  \l__tblr_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__tblr_column_natural_width_prop
      { \dim_add:Nn \l_tmpa_dim { ##2 } }
    \tl_set:Ne \l_tmpa_tl
      { \dim_ratio:nn { \l__tblr_column_target_dim } { \l_tmpa_dim } }
    \dim_zero:N \l__tblr_column_target_dim
    \prop_map_inline:Nn \l__tblr_column_natural_width_prop
      {
        \tl_set:Ne \l_tmpb_tl { \dim_eval:n { ##2 * \l_tmpa_tl } }
        \__tblr_add_dimen_value:Nnn
          \l__tblr_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 { inner } { colspan }
      {
        \__tblr_collect_column_widths_skips:
        \str_if_eq:enTF
          { \__tblr_prop_item:ne { inner } { 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 { inner } { 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@colcount } \l__tblr_j_tl
      {
        \int_compare:nNnTF { \l__tblr_j_tl } > { 1 }
          {
            \prop_put:Nee \l__tblr_col_item_skip_size_prop { skip[\l__tblr_j_tl] }
              {
                \dim_eval:n
                  {
                    \__tblr_data_item:nen { column }
                      { \int_eval:n { \l__tblr_j_tl - 1 } } { rightsep }
                    +
                    \__tblr_spec_item:ne { vline }
                      { [\l__tblr_j_tl] / @vline-width }
                    +
                    \__tblr_data_item:nen { column } { \l__tblr_j_tl } { leftsep }
                  }
              }
          }
          {
            \prop_put:Nen \l__tblr_col_item_skip_size_prop { skip[\l__tblr_j_tl] }
              { 0pt }
          }
        \prop_put:Nee \l__tblr_col_item_skip_size_prop { item[\l__tblr_j_tl] }
          { \__tblr_data_item:nen { 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@rowcount } \l__tblr_i_tl
      {
        \int_compare:nNnTF { \l__tblr_i_tl } > { 1 }
          {
            \prop_put:Nee \l__tblr_row_item_skip_size_prop { skip[\l__tblr_i_tl] }
              {
                \dim_eval:n
                  {
                    \__tblr_data_item:nen { row }
                      { \int_eval:n {\l__tblr_i_tl - 1} } { belowsep }
                    +
                    \__tblr_spec_item:ne { hline }
                      { [\l__tblr_i_tl] / @hline-height }
                    +
                    \__tblr_data_item:nen { row } { \l__tblr_i_tl } { abovesep }
                  }
              }
          }
          {
            \prop_put:Nen \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:Nee \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:Ne #2 { \__tblr_data_item:nen { 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@colcount } \l__tblr_j_tl
      {
        \int_step_variable:nNn { \c@rowcount } \l__tblr_i_tl
          {
            \tl_set:Ne \l__tblr_a_tl
              {
                \__tblr_data_item:neen { cell }
                  { \l__tblr_i_tl } { \l__tblr_j_tl } { colspan }
              }
            \int_compare:nNnT { \l__tblr_a_tl } > {1}
              {
                \__tblr_put_if_larger:Nee \l__tblr_col_span_size_prop
                  {
                    ( \l__tblr_j_tl -
                      \int_eval:n {\l__tblr_j_tl + \l__tblr_a_tl - 1} )
                  }
                  {
                    \__tblr_data_item:neen { 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@rowcount } \l__tblr_i_tl
      {
        \int_step_variable:nNn { \c@colcount } \l__tblr_j_tl
          {
            \tl_set:Ne \l__tblr_a_tl
              {
                \__tblr_data_item:neen { cell }
                  { \l__tblr_i_tl } { \l__tblr_j_tl } { rowspan }
              }
            \int_compare:nNnT { \l__tblr_a_tl } > {1}
              {
                \tl_set:Ne \l__tblr_v_tl
                  {
                    \__tblr_data_item:neen { cell }
                      { \l__tblr_i_tl } { \l__tblr_j_tl } { valign }
                  }
                \tl_if_eq:NnT \l__tblr_v_tl { h }
                  {
                    \tl_set:Ne \l__tblr_h_tl
                      {
                        \__tblr_data_item:nen { row }
                          { \l__tblr_i_tl } { @row-head }
                      }
                    \__tblr_data_gput:neenV { 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:Ne \l__tblr_d_tl
                      {
                        \__tblr_data_item:nen
                          { row }
                          { \int_eval:n { \l__tblr_i_tl + \l__tblr_a_tl - 1 } }
                          { @row-foot }
                      }
                    \__tblr_data_gput:neenV { cell }
                      { \l__tblr_i_tl } { \l__tblr_j_tl } { @cell-depth }
                      \l__tblr_d_tl
                  }
                \__tblr_put_if_larger:Nee \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_data_item:neen { cell }
                          { \l__tblr_i_tl } { \l__tblr_j_tl } { @cell-height }
                        +
                        \__tblr_data_item:neen { cell }
                          { \l__tblr_i_tl } { \l__tblr_j_tl } { @cell-depth }
                      }
                  }
                \prop_put:Nee \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:
  {
    \str_if_eq:enTF
      { \__tblr_prop_item:ne { inner } { hspan } }
      { even }
      {
        \__tblr_distribute_span_sizes_even:eNN
          { \int_use:N \c@colcount }
          \l__tblr_col_item_skip_size_prop
          \l__tblr_col_span_size_prop
      }
      {
        \__tblr_distribute_span_sizes_default:eNN
          { \int_use:N \c@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:
  {
    \str_if_eq:enTF
      { \__tblr_prop_item:ne { inner } { vspan } }
      { even }
      {
        \__tblr_distribute_span_sizes_even:nNN
          { \int_use:N \c@rowcount }
          \l__tblr_row_item_skip_size_prop
          \l__tblr_row_span_size_prop
      }
      {
        \__tblr_distribute_span_sizes_default:eNN
          { \int_use:N \c@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_distribute_span_sizes_default: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:Ne \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:e
                          { \l__tblr_j_tl : \l__tblr_i_tl -> \l__tblr_k_tl }
                      }
                    \tl_set:Ne \l_tmpa_tl
                      {
                        \prop_item:Ne #2 { itemskip[\l__tblr_k_tl] }
                      }
                    \tl_set:Ne \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:Nee #2
          { item[\l__tblr_j_tl] } { \dim_use:N \l__tblr_w_dim }
        \int_compare:nNnT { \l__tblr_j_tl } < { #1 }
          {
            \tl_set:Ne \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:Nee #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_distribute_span_sizes_default:nNN { e }

%% #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_distribute_span_sizes_even:nNN #1 #2 #3
  {
    \prop_clear:N \l_tmpa_prop
    \prop_map_inline:Nn #3
      {
        \__tblr_get_span_from_to:w ##1
        \dim_set:Nn \l_tmpa_dim {##2}
        \dim_sub:Nn \l_tmpa_dim { \prop_item:Ne #2 { item[\l__tblr_a_tl] } }
        \int_step_inline:nnn { \l__tblr_a_tl + 1 } { \l__tblr_b_tl }
          {
            \dim_sub:Nn \l_tmpa_dim
              {
                \prop_item:Ne #2 { skip[####1] } + \prop_item:Nn #2 { item[####1] }
              }
          }
        \__tblr_do_if_tracing:nn { cellspan }
          {
            \tl_log:e { \l__tblr_a_tl -> \l__tblr_b_tl : ~ \dim_use:N \l_tmpa_dim }
          }
        \dim_compare:nNnT {\l_tmpa_dim} > {0pt}
          {
            \tl_set:Ne \l_tmpa_tl
              { \dim_eval:n { \l_tmpa_dim / (\l__tblr_b_tl - \l__tblr_a_tl + 1) } }
            \int_step_inline:nnn { \l__tblr_a_tl } { \l__tblr_b_tl }
              {
                \__tblr_put_if_larger:NnV \l_tmpa_prop {####1} \l_tmpa_tl
              }
          }
      }
    \__tblr_do_if_tracing:nn { cellspan } { \prop_log:N \l_tmpa_prop }
    \prop_map_inline:Nn \l_tmpa_prop
      {
        \__tblr_add_dimen_value:Nnn #2 {item[##1]} {##2}
      }
    \__tblr_do_if_tracing:nn { cellspan } { \prop_log:N #2 }
  }
\cs_generate_variant:Nn \__tblr_distribute_span_sizes_even:nNN { e }

\cs_new_protected:Npn \__tblr_get_span_from_to:w (#1-#2)
  {
    \tl_set:Nn \l__tblr_a_tl {#1}
    \tl_set:Nn \l__tblr_b_tl {#2}
  }

\cs_new_protected:Npn \__tblr_set_all_column_widths:
  {
    \int_step_variable:nNn { \c@colcount } \l__tblr_j_tl
      {
        \__tblr_data_gput:nene { column }
          { \l__tblr_j_tl } { 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@rowcount } \l__tblr_i_tl
      {
        \tl_set:Ne \l__tblr_h_tl
          {
            \__tblr_data_item:nen { row } { \l__tblr_i_tl } { @row-head }
          }
        \tl_set:Ne \l__tblr_d_tl
          {
            \__tblr_data_item:nen { row } { \l__tblr_i_tl } { @row-foot }
          }
        \tl_set:Ne \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_data_gput:nene { row }
          { \l__tblr_i_tl } { @row-height } { \l__tblr_a_tl }
      }
  }

%% Compute and set span widths from column widths
\cs_new_protected:Npn \__tblr_set_span_widths_from_column_widths:
  {
    \int_step_variable:nNn { \c@colcount } \l__tblr_j_tl
      {
        \int_step_variable:nNn { \c@rowcount } \l__tblr_i_tl
          {
            \tl_set:Ne \l__tblr_a_tl
              {
                \__tblr_data_item:neen { cell }
                  { \l__tblr_i_tl } { \l__tblr_j_tl } { colspan }
              }
            \int_compare:nNnT { \l__tblr_a_tl } > {1}
              {
                \__tblr_calc_span_widths:eeN
                  { \l__tblr_j_tl }
                  { \int_eval:n { \l__tblr_j_tl + \l__tblr_a_tl - 1 } }
                  \l__tblr_w_dim
                \__tblr_data_gput:neene { 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_set:Nn #3 { \prop_item:Ne \l__tblr_col_item_skip_size_prop { item[#1] } }
    \int_step_inline:nnn { #1 + 1 } { #2 }
      {
        \tl_set:Ne \l_tmpa_tl
          { \prop_item:Ne \l__tblr_col_item_skip_size_prop { skip[##1] } }
        \tl_set:Ne \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 { ee }

%%% --------------------------------------------------------
%%> \section{Header and footer styles}
%%% --------------------------------------------------------

\prop_new:N \l__tblr_element_styles_prop

\cs_new_protected:Npn \__tblr_style_put:nn #1 #2
  {
    \prop_put:Nnn \l__tblr_element_styles_prop {#1} {#2}
  }
\cs_generate_variant:Nn \__tblr_style_put:nn { nV, ne, en, eV }

\cs_new:Npn \__tblr_style_item:n #1
  {
    \prop_item:Nn \l__tblr_element_styles_prop {#1}
  }

\cs_new_protected:Npn \__tblr_style_log:
  {
    \prop_log:N \l__tblr_element_styles_prop
  }

\tl_new:N \l__tblr_element_name_tl
\tl_new:N \l__tblr_element_styles_tl

%% #1: list of element names; #2: element styles
\NewDocumentCommand \SetTblrStyle { m +m }
  {
    \tl_set:Nn \l__tblr_element_styles_tl {#2}
    \__tblr_keys_set:nn { template/element } {#1}
    \ignorespaces
  }

\__tblr_keys_define:nn { template/element }
  {
    head    .meta:n = { firsthead, middlehead, lasthead },
    foot    .meta:n = { firstfoot, middlefoot, lastfoot },
    unknown .code:n = \__tblr_set_element_styles:V \l_keys_key_str,
  }

\cs_new_protected:Npn \__tblr_set_element_styles:n #1
  {
    \tl_set:Nn \l__tblr_element_name_tl {#1}
    \__tblr_keys_set:nV { template/style } \l__tblr_element_styles_tl
  }
\cs_generate_variant:Nn \__tblr_set_element_styles:n { V }

\__tblr_keys_define:nn { template/style }
  {
    halign  .code:n = \__tblr_element_gput_style:nn { halign } {#1},
    l       .meta:n = { halign = l },
    c       .meta:n = { halign = c },
    r       .meta:n = { halign = r },
    j       .meta:n = { halign = j },
    fg      .code:n = \__tblr_element_gput_style:nn { fg } {#1},
    font    .code:n = \__tblr_element_gput_style:nn { font } {#1},
    hang    .code:n = \__tblr_element_gput_style:nn { hang } {#1},
    indent  .code:n = \__tblr_element_gput_style:nn { indent } {#1},
    unknown .code:n = \__tblr_element_unknown_key:Vn \l_keys_key_str {#1},
  }

\cs_new_protected:Npn \__tblr_element_gput_style:nn #1 #2
  {
    \__tblr_style_put:en { \l__tblr_element_name_tl / #1 } {#2}
  }

\cs_new_protected:Npn \__tblr_element_unknown_key:nn #1 #2
  {
    \__tblr_if_color_value:nTF {#1}
      { \__tblr_style_put:en { \l__tblr_element_name_tl / fg } {#1} }
      {
        %% unknown key name has been changed to string in \l_keys_key_str
        \tl_set_rescan:Nnn \l__tblr_f_tl {} {#1}
        \tl_if_head_eq_catcode:VNTF \l__tblr_f_tl \scan_stop:
          {
            \__tblr_style_put:eV { \l__tblr_element_name_tl / font } \l__tblr_f_tl
          }
          {
            \__tblr_style_put:en { \l__tblr_element_name_tl / #1 } {#2}
          }
      }
  }
\cs_generate_variant:Nn \__tblr_element_unknown_key:nn { Vn }

%%% --------------------------------------------------------
%%> \section{Helper functions for templates}
%%% --------------------------------------------------------

\tl_new:N \l__tblr_template_name_tl
\tl_new:N \l__tblr_template_code_tl

\__tblr_keys_define:nn { template/def }
  {
    unknown .code:n = \__tblr_def_template:V \l_keys_key_str,
  }

%% #1: head/foot element; #2: template name; #3: template code
%% If the template name = default, we enable the template at once
%% Otherwise, we may enable the template by using \SetTblrTemplate command
\NewDocumentCommand \DeclareTblrTemplate { m m +m }
  {
    \tl_set:Nn \l__tblr_template_name_tl {#2}
    \tl_set:Nn \l__tblr_template_code_tl {#3}
    \__tblr_keys_set:nn { template/def } {#1}
    \ignorespaces
  }
\cs_new_eq:NN \DefTblrTemplate \DeclareTblrTemplate

\cs_new_protected:Npn \__tblr_def_template:n #1
  {
    \tl_if_exist:cF { l__tblr_template_ #1 _ \l__tblr_template_name_tl _tl }
      { \tl_new:c { l__tblr_template_ #1 _ \l__tblr_template_name_tl _tl } }
    \tl_set_eq:cN { l__tblr_template_ #1 _ \l__tblr_template_name_tl _tl }
      \l__tblr_template_code_tl
  }
\cs_generate_variant:Nn \__tblr_def_template:n { V }

\__tblr_keys_define:nn { template/set }
  {
    unknown .code:n = \__tblr_set_template:V \l_keys_key_str,
  }

%% #1: head/foot element; #2: template name
\NewDocumentCommand \SetTblrTemplate { m m }
  {
    \tl_set:Nn \l__tblr_template_name_tl {#2}
    \__tblr_keys_set:nn { template/set } {#1}
    \ignorespaces
  }

\msg_new:nnn { tabularray } { template-undefined }
  {
    Undefined ~ template ~ "#2" ~ for ~ element ~ "#1".
  }
\cs_new_protected:Npn \__tblr_set_template:n #1
  {
    \tl_if_exist:cTF { l__tblr_template_ #1 _ \l__tblr_template_name_tl _tl }
      {
        \tl_if_exist:cF { l__tblr_template_ #1 _default_tl }
          { \tl_new:c { l__tblr_template_ #1 _default_tl } }
        \tl_set_eq:cc { l__tblr_template_ #1 _default_tl }
          { l__tblr_template_ #1 _ \l__tblr_template_name_tl _tl }
      }
      {
        \msg_error:nnee { tabularray } { template-undefined }
          { #1 } { \l__tblr_template_name_tl }
      }
  }
\cs_generate_variant:Nn \__tblr_set_template:n { V }

\NewExpandableDocumentCommand \GetTblrStyle { m m }
  {
    \__tblr_style_item:n { #1 / #2 }
  }

\NewDocumentCommand \UseTblrFont { m }
  {
    \GetTblrStyle {#1} { font } \selectfont
  }

\tl_new:N \l__tblr_use_color_tl

\NewDocumentCommand \UseTblrColor { m }
  {
    \tl_set:Ne \l__tblr_use_color_tl { \GetTblrStyle {#1} { fg } }
    \tl_if_empty:NF \l__tblr_use_color_tl { \color { \l__tblr_use_color_tl } }
  }

%% All halign commands are defined at the beginning of the file
\NewDocumentCommand \UseTblrAlign { m }
  {
    \use:c { __tblr_halign_command_ \GetTblrStyle {#1} { halign } : }
  }

\tl_new:N \l__tblr_use_hang_tl

\NewDocumentCommand \UseTblrHang { m }
  {
    \tl_set:Ne \l__tblr_use_hang_tl { \GetTblrStyle {#1} { hang } }
    \tl_if_empty:NF \l__tblr_use_hang_tl
      {
        \tl_put_left:Nn \l__tblr_use_hang_tl
          { \hangafter = 1 \relax \hangindent = }
        \tl_put_right:Nn \l__tblr_use_hang_tl { \relax }
        \exp_args:NV \everypar \l__tblr_use_hang_tl
      }
  }

\tl_new:N \l__tblr_use_indent_tl

\NewDocumentCommand \UseTblrIndent { m }
  {
    \tl_set:Ne \l__tblr_use_indent_tl { \GetTblrStyle {#1} { indent } }
    \tl_if_empty:NF \l__tblr_use_indent_tl
      { \exp_args:NNV \setlength \parindent \l__tblr_use_indent_tl }
  }

\AtBeginDocument
  {
    \@ifpackageloaded{xcolor}{}{\RenewDocumentCommand \UseTblrColor {m} {}}
  }

%% #1: head/foot element; #2: template name
\NewExpandableDocumentCommand \ExpTblrTemplate { m m }
  {
    \tl_use:c { l__tblr_template_ #1 _ #2 _tl }
  }

%% #1: head/foot element; #2: template name
\NewDocumentCommand \UseTblrTemplate { m m }
  {
    \group_begin:
    \UseTblrFont {#1}
    \UseTblrColor {#1}
    \tl_use:c { l__tblr_template_ #1 _ #2 _tl }
    \group_end:
  }

\NewDocumentCommand \MapTblrNotes { +m }
  {
    \__tblr_prop_map_inline:nn { note }
      {
        \tl_set_rescan:Nnn \InsertTblrNoteTag {} {##1}
        \tl_set:Nn \InsertTblrNoteText {##2}
        #1
      }
  }

\NewDocumentCommand \MapTblrRemarks { +m }
  {
    \__tblr_prop_map_inline:nn { remark }
      {
        \tl_set_rescan:Nnn \InsertTblrRemarkTag {} {##1}
        \tl_set:Nn \InsertTblrRemarkText {##2}
        #1
      }
  }

\NewExpandableDocumentCommand \InsertTblrText { m }
  {
    \__tblr_spec_item:nn { outer } {#1}
  }

\NewExpandableDocumentCommand \InsertTblrMore { m }
  {
    \__tblr_prop_item:nn { more } {#1}
  }

%%% --------------------------------------------------------
%%> \section{Table continuation templates}
%%% --------------------------------------------------------

\tl_if_exist:NF \tblrcontfootname
  {
    \tl_new:N \tblrcontfootname
    \tl_set:Nn \tblrcontfootname { Continued ~ on ~ next ~ page }
  }

\tl_if_exist:NF \tblrcontheadname
  {
    \tl_new:N \tblrcontheadname
    \tl_set:Nn \tblrcontheadname { ( Continued ) }
  }

\DeclareTblrTemplate { contfoot-text } { normal } { \tblrcontfootname }
\SetTblrTemplate { contfoot-text } { normal }

\DeclareTblrTemplate { contfoot } { empty } { }
\DeclareTblrTemplate { contfoot } { plain }
  {
    \noindent
    \raggedleft
    \UseTblrTemplate { contfoot-text } { default }
    \par
  }
\DeclareTblrTemplate { contfoot } { normal }
  {
    %% need to set parindent after alignment
    \raggedleft
    \UseTblrAlign { contfoot }
    \UseTblrIndent { contfoot }
    \UseTblrHang { contfoot }
    \leavevmode
    \UseTblrTemplate { contfoot-text } { default }
    \par
  }
\SetTblrTemplate { contfoot } { normal }

\DeclareTblrTemplate { conthead-pre } { empty } { }
\DeclareTblrTemplate { conthead-pre } { normal } { \space }
\SetTblrTemplate { conthead-pre } { normal }

\DeclareTblrTemplate { conthead-text } { normal } { \tblrcontheadname }
\SetTblrTemplate { conthead-text } { normal }

\DeclareTblrTemplate { conthead } { empty } { }
\DeclareTblrTemplate { conthead } { plain }
  {
    \noindent
    \raggedright
    \UseTblrTemplate { conthead-text } { default }
    \par
  }
\DeclareTblrTemplate { conthead } { normal }
  {
    %% need to set parindent after alignment
    \raggedright
    \UseTblrAlign { conthead }
    \UseTblrIndent { conthead }
    \UseTblrHang { conthead }
    \leavevmode
    \UseTblrTemplate { conthead-text } { default }
    \par
  }
\SetTblrTemplate { conthead } { normal }

%%% --------------------------------------------------------
%%> \section{Table caption templates}
%%% --------------------------------------------------------

\tl_new:N \l__tblr_caption_short_tl

\DeclareTblrTemplate { caption-lot } { empty } { }
\DeclareTblrTemplate { caption-lot } { normal }
  {
    \tl_if_empty:NTF \lTblrEntryTl
      { \tl_set_eq:NN \l__tblr_caption_short_tl \lTblrCaptionTl }
      { \tl_set_eq:NN \l__tblr_caption_short_tl \lTblrEntryTl }
    \addcontentsline { lot } { table }
      { \protect\numberline { \thetable } { \l__tblr_caption_short_tl } }
  }
\SetTblrTemplate { caption-lot } { normal }

%% We need to use \hspace and \enskip, but not ~ or \space,
%% since we want a correct hangindent caption paragraph.

\DeclareTblrTemplate { caption-tag } { empty } { }
\DeclareTblrTemplate { caption-tag } { normal } { \tablename\hspace{0.25em}\thetable }
\SetTblrTemplate { caption-tag } { normal }

\DeclareTblrTemplate { caption-sep } { empty } { }
\DeclareTblrTemplate { caption-sep } { normal } { : \enskip }
\SetTblrTemplate { caption-sep } { normal }

\DeclareTblrTemplate { caption-text } { empty } { }
\DeclareTblrTemplate { caption-text } { normal } { \InsertTblrText { caption } }
\SetTblrTemplate { caption-text } { normal }

\box_new:N \l__tblr_caption_box
\box_new:N \l__tblr_caption_left_box

\DeclareTblrTemplate { caption } { empty } { }
\DeclareTblrTemplate { caption } { plain }
  {
    \hbox_set:Nn \l__tblr_caption_box
      {
        \UseTblrTemplate { caption-tag } { default }
        \UseTblrTemplate { caption-sep } { default }
        \UseTblrTemplate { caption-text } { default }
      }
    \dim_compare:nNnTF { \box_wd:N \l__tblr_caption_box } > { \hsize }
      {
        \noindent
        \hbox_unpack:N \l__tblr_caption_box
        \par
      }
      {
        \centering
        \makebox [\hsize] [c] { \box_use:N \l__tblr_caption_box }
        \par
      }
  }
\DeclareTblrTemplate { caption } { normal }
  {
    \hbox_set:Nn \l__tblr_caption_box
      {
        \UseTblrTemplate { caption-tag } { default }
        \UseTblrTemplate { caption-sep } { default }
        \UseTblrTemplate { caption-text } { default }
      }
    \dim_compare:nNnTF { \box_wd:N \l__tblr_caption_box } > { \hsize }
      {
        \UseTblrAlign { caption }
        \UseTblrIndent { caption }
        \hbox_set:Nn \l__tblr_caption_left_box
          {
            \UseTblrTemplate { caption-tag } { default }
            \UseTblrTemplate { caption-sep } { default }
          }
        \hangindent = \box_wd:N \l__tblr_caption_left_box
        \hangafter = 1
        \UseTblrHang { caption }
        \leavevmode
        \hbox_unpack:N \l__tblr_caption_box
        \par
      }
      {
        \centering
        \makebox [\hsize] [c] { \box_use:N \l__tblr_caption_box }
        \par
      }
  }
\DeclareTblrTemplate { caption } { simple }
  {
    \UseTblrAlign { caption }
    \UseTblrIndent { caption }
    \UseTblrHang { caption }
    \leavevmode
    \UseTblrTemplate { caption-tag } { default }
    \UseTblrTemplate { caption-sep } { default }
    \UseTblrTemplate { caption-text } { default }
    \par
  }
\SetTblrTemplate { caption } { normal }

\DeclareTblrTemplate { capcont } { empty } { }
\DeclareTblrTemplate { capcont } { plain }
  {
    \hbox_set:Nn \l__tblr_caption_box
      {
        \UseTblrTemplate { caption-tag } { default }
        \UseTblrTemplate { caption-sep } { default }
        \UseTblrTemplate { caption-text } { default }
        \UseTblrTemplate { conthead-pre } { default }
        \UseTblrTemplate { conthead-text } { default }
      }
    \dim_compare:nNnTF { \box_wd:N \l__tblr_caption_box } > { \hsize }
      {
        \noindent
        \hbox_unpack:N \l__tblr_caption_box
        \par
      }
      {
        \centering
        \makebox [\hsize] [c] { \box_use:N \l__tblr_caption_box }
        \par
      }
  }
\DeclareTblrTemplate { capcont } { normal }
  {
    \hbox_set:Nn \l__tblr_caption_box
      {
        \UseTblrTemplate { caption-tag } { default }
        \UseTblrTemplate { caption-sep } { default }
        \UseTblrTemplate { caption-text } { default }
        \UseTblrTemplate { conthead-pre } { default }
        \UseTblrTemplate { conthead-text } { default }
      }
    \dim_compare:nNnTF { \box_wd:N \l__tblr_caption_box } > { \hsize }
      {
        \UseTblrAlign { capcont }
        \UseTblrIndent { capcont }
        \hbox_set:Nn \l__tblr_caption_left_box
          {
            \UseTblrTemplate { caption-tag } { default }
            \UseTblrTemplate { caption-sep } { default }
          }
        \hangindent = \box_wd:N \l__tblr_caption_left_box
        \hangafter = 1
        \UseTblrHang { capcont }
        \leavevmode
        \hbox_unpack:N \l__tblr_caption_box
        \par
      }
      {
        \centering
        \makebox [\hsize] [c] { \box_use:N \l__tblr_caption_box }
        \par
      }
  }
\DeclareTblrTemplate { capcont } { simple }
  {
    \UseTblrAlign { caption }
    \UseTblrIndent { caption }
    \UseTblrHang { caption }
    \leavevmode
    \UseTblrTemplate { caption-tag } { default }
    \UseTblrTemplate { caption-sep } { default }
    \UseTblrTemplate { caption-text } { default }
    \UseTblrTemplate { conthead-pre } { default }
    \UseTblrTemplate { conthead-text } { default }
    \par
  }
\SetTblrTemplate { capcont} { normal }

%%% --------------------------------------------------------
%%> \section{Table notes templates}
%%% --------------------------------------------------------

%% By default the targets generated by \hypertarget are too low
%% Therefore we need to use \Hy@raisedlink command to fix this problem
%% See https://tex.stackexchange.com/questions/17057
%% We also use \use:c in case the private command \Hy@raisedlink is removed
\cs_new_protected:Npn \__tblr_hyper_target:n #1
  {
    \cs_if_exist:NT \hypertarget
      {
        \use:c { Hy@raisedlink }
          {
            \hypertarget
              { tblr / \int_use:N \c@tblrcount / \tl_to_str:n {#1} }
              { }
          }
      }
  }
\cs_generate_variant:Nn \__tblr_hyper_target:n { V }

\cs_new_protected:Npn \__tblr_hyper_link:nn #1 #2
  {
    \cs_if_exist:NTF \hyperlink
      {
        \hyperlink
          { tblr / \int_use:N \c@tblrcount / \tl_to_str:n {#1} }
          { #2 }
      }
      { #2 }
  }

\DeclareTblrTemplate { note-border } { empty }
  {
    \hypersetup { pdfborder = { 0 ~ 0 ~ 0 } }
  }
\DeclareTblrTemplate { note-border } { normal }
  {
    \hypersetup { pdfborder = { 0 ~ 0 ~ 1 } }
  }
\SetTblrTemplate { note-border } { empty }

\cs_set_eq:NN \TblrOverlap \rlap

\NewDocumentCommand \TblrNote { m }
  {
    \cs_if_exist:NT \hypersetup { \ExpTblrTemplate { note-border }{ default } }
    \TblrOverlap
      {
        \__tblr_hyper_link:nn {#1}
          { \textsuperscript { \sffamily \UseTblrFont { note-tag } #1 } }
      }
  }

\DeclareTblrTemplate { note-tag } { empty } { }
\DeclareTblrTemplate { note-tag } { normal }
  {
    \textsuperscript { \sffamily \UseTblrFont { note-tag } \InsertTblrNoteTag }
  }
\SetTblrTemplate { note-tag } { normal }

\DeclareTblrTemplate { note-target } { normal }
  {
    \__tblr_hyper_target:V \InsertTblrNoteTag
  }
\SetTblrTemplate { note-target } { normal }

\DeclareTblrTemplate { note-sep } { empty } { }
\DeclareTblrTemplate { note-sep } { normal } { \space }
\SetTblrTemplate { note-sep } { normal }

\DeclareTblrTemplate { note-text } { empty } { }
\DeclareTblrTemplate { note-text } { normal } { \InsertTblrNoteText }
\SetTblrTemplate { note-text } { normal }

\DeclareTblrTemplate { note } { empty } { }
\DeclareTblrTemplate { note } { plain }
  {
    \MapTblrNotes
      {
        \noindent
        \UseTblrTemplate { note-tag } { default }
        \UseTblrTemplate { note-target } { default }
        \UseTblrTemplate { note-sep } { default }
        \UseTblrTemplate { note-text } { default }
        \par
      }
  }
\DeclareTblrTemplate { note } { normal }
  {
    \UseTblrAlign { note }
    \UseTblrIndent { note }
    \MapTblrNotes
      {
        \hangindent = 0.7em
        \hangafter = 1
        \UseTblrHang { note }
        \leavevmode
        \hbox_to_wd:nn { \the\hangindent }
          {
            \UseTblrTemplate { note-tag } { default }
            \UseTblrTemplate { note-target } { default }
            \hfil
          }
        \UseTblrTemplate { note-text } { default }
        \par
      }
  }
\DeclareTblrTemplate { note } { inline }
  {
    \UseTblrAlign { note }
    \UseTblrIndent { note }
    \UseTblrHang { note }
    \leavevmode
    \MapTblrNotes
      {
        \UseTblrTemplate { note-tag } { default }
        \UseTblrTemplate { note-target } { default }
        \UseTblrTemplate { note-sep } { default }
        \UseTblrTemplate { note-text } { default }
        \quad
      }
    \par
  }
\SetTblrTemplate { note } { normal }

%%% --------------------------------------------------------
%%> \section{Table remarks templates}
%%% --------------------------------------------------------

\DeclareTblrTemplate { remark-tag } { empty } { }
\DeclareTblrTemplate { remark-tag } { normal }
  {
    \itshape \UseTblrFont { remark-tag } \InsertTblrRemarkTag
  }
\SetTblrTemplate { remark-tag } { normal }

\DeclareTblrTemplate { remark-sep } { empty } { }
\DeclareTblrTemplate { remark-sep } { normal } { : \space }
\SetTblrTemplate { remark-sep } { normal }

\DeclareTblrTemplate { remark-text } { empty } { }
\DeclareTblrTemplate { remark-text } { normal } { \InsertTblrRemarkText }
\SetTblrTemplate { remark-text } { normal }

\DeclareTblrTemplate { remark } { empty } { }
\DeclareTblrTemplate { remark } { plain }
  {
    \MapTblrRemarks
      {
        \noindent
        \UseTblrTemplate { remark-tag } { default }
        \UseTblrTemplate { remark-sep } { default }
        \UseTblrTemplate { remark-text } { default }
        \par
      }
  }
\DeclareTblrTemplate { remark } { normal }
  {
    \UseTblrAlign { remark }
    \UseTblrIndent { remark }
    \MapTblrRemarks
      {
        \hangindent = 0.7em
        \hangafter = 1
        \UseTblrHang { remark }
        \leavevmode
        \UseTblrTemplate { remark-tag } { default }
        \UseTblrTemplate { remark-sep } { default }
        \UseTblrTemplate { remark-text } { default }
        \par
      }
  }
\DeclareTblrTemplate { remark } { inline }
  {
    \UseTblrAlign { remark }
    \UseTblrIndent { remark }
    \UseTblrHang { remark }
    \leavevmode
    \MapTblrRemarks
      {
        \UseTblrTemplate { remark-tag } { default }
        \UseTblrTemplate { remark-sep } { default }
        \UseTblrTemplate { remark-text } { default }
        \quad
      }
    \par
  }
\SetTblrTemplate { remark } { normal }

%%% --------------------------------------------------------
%%> \section{Header and footer templates}
%%% --------------------------------------------------------

\tl_new:N \g__tblr_template_firsthead_default_tl
\tl_new:N \g__tblr_template_middlehead_default_tl
\tl_new:N \g__tblr_template_lasthead_default_tl
\tl_new:N \g__tblr_template_firstfoot_default_tl
\tl_new:N \g__tblr_template_middlefoot_default_tl
\tl_new:N \g__tblr_template_lastfoot_default_tl

\__tblr_keys_define:nn { template/def }
  {
    head .meta:n = { firsthead, middlehead, lasthead },
    foot .meta:n = { firstfoot, middlefoot, lastfoot },
  }

\__tblr_keys_define:nn { template/set }
  {
    head .meta:n = { firsthead, middlehead, lasthead },
    foot .meta:n = { firstfoot, middlefoot, lastfoot },
  }

\DeclareTblrTemplate { head } { empty } { }
\DeclareTblrTemplate { foot } { empty } { }

\DeclareTblrTemplate { firsthead } { normal }
  {
    \UseTblrTemplate { caption } { default }
  }

\DeclareTblrTemplate { middlehead, lasthead } { normal }
  {
    \UseTblrTemplate { capcont } { default }
  }

\DeclareTblrTemplate { firstfoot, middlefoot } { normal }
  {
    \UseTblrTemplate { contfoot } { default }
  }

\DeclareTblrTemplate { lastfoot } { normal }
  {
    \UseTblrTemplate { note } { default }
    \UseTblrTemplate { remark } { default }
  }

\SetTblrTemplate { head } { normal }
\SetTblrTemplate { foot } { normal }

%%% --------------------------------------------------------
%%> \section{Build the whole table}
%%% --------------------------------------------------------

\cs_new:Npn \__tblr_box_height:N #1
  {
    \dim_eval:n { \box_ht:N #1 + \box_dp:N #1 }
  }

\cs_new_protected:Npn \__tblr_build_head_foot:
  {
    \__tblr_build_row_head_foot:
    \__tblr_build_table_head_foot:
  }

\int_new:N \lTblrRowHeadInt
\int_new:N \lTblrRowFootInt
\box_new:N \l__tblr_row_head_box
\box_new:N \l__tblr_row_foot_box
\dim_new:N \l__tblr_row_head_foot_dim

\cs_new_protected:Npn \__tblr_build_row_head_foot:
  {
    %% \lTblrRowHeadInt could not be empty, so we append '+ 0'.
    \int_set:Nn \lTblrRowHeadInt
      { \__tblr_prop_item:ne { inner } { rowhead } + 0 }
    \int_compare:nNnTF { \lTblrRowHeadInt } > { 0 }
      {
        \__tblr_build_one_table:nnNN {1} { \lTblrRowHeadInt }
          \c_true_bool \c_true_bool
      }
      { \__tblr_build_one_hline:n {1} }
    \box_set_eq:NN \l__tblr_row_head_box \l__tblr_table_box
    %% \lTblrRowFootInt could not be empty, so we append '+ 0'.
    \int_set:Nn \lTblrRowFootInt
      { \__tblr_prop_item:ne { inner } { rowfoot } + 0 }
    \int_compare:nNnTF { \lTblrRowFootInt } > { 0 }
      {
        \__tblr_build_one_table:nnNN
          { \c@rowcount - \lTblrRowFootInt + 1 } { \c@rowcount }
          \c_true_bool \c_true_bool
      }
      { \__tblr_build_one_hline:n { \int_eval:n { \c@rowcount + 1 } } }
    \box_set_eq:NN \l__tblr_row_foot_box \l__tblr_table_box
    \dim_set:Nn \l__tblr_row_head_foot_dim
      {
        \__tblr_box_height:N \l__tblr_row_head_box
          + \__tblr_box_height:N \l__tblr_row_foot_box
      }
  }

\dim_new:N \lTblrTableWidthDim
\cs_set_eq:NN \tablewidth \lTblrTableWidthDim

\cs_new_protected:Npn \__tblr_get_table_width:
  {
    \dim_zero:N \lTblrTableWidthDim
    \int_step_inline:nn { \c@colcount }
      {
        \dim_add:Nn \lTblrTableWidthDim
          {
            \__tblr_spec_item:nn { vline } { [##1] / @vline-width }
            +
            \__tblr_data_item:nnn { column } {##1} { leftsep }
            +
            \__tblr_data_item:nnn { column } {##1} { @col-width }
            +
            \__tblr_data_item:nnn { column } {##1} { rightsep }
          }
      }
    \dim_add:Nn \lTblrTableWidthDim
      {
        \__tblr_spec_item:ne { vline }
          { [\int_eval:n { \c@colcount + 1 }] / @vline-width }
      }
  }

\box_new:N \l__tblr_table_firsthead_box
\box_new:N \l__tblr_table_middlehead_box
\box_new:N \l__tblr_table_lasthead_box
\box_new:N \l__tblr_table_firstfoot_box
\box_new:N \l__tblr_table_middlefoot_box
\box_new:N \l__tblr_table_lastfoot_box

\cs_new_protected:Npn \__tblr_build_table_head_foot:
  {
    \__tblr_get_table_width:
    % make each of \lTblrCaptionTl, \lTblrEntryTl, \lTblrLabelTl and the
    % three corresponding booleans available in all head-foot templates
    \__tblr_set_table_label_entry:
    \__tblr_build_table_head_aux:Nn \l__tblr_table_firsthead_box
      {
        \__tblr_build_table_label_entry:
        \UseTblrTemplate { firsthead } { default }
      }
    \__tblr_build_table_head_aux:Nn \l__tblr_table_middlehead_box
      {
        \UseTblrTemplate { middlehead } { default }
      }
    \__tblr_build_table_head_aux:Nn \l__tblr_table_lasthead_box
      {
        \UseTblrTemplate { lasthead } { default }
      }
    \__tblr_build_table_foot_aux:Nn \l__tblr_table_firstfoot_box
      {
        \UseTblrTemplate { firstfoot } { default }
      }
    \__tblr_build_table_foot_aux:Nn \l__tblr_table_middlefoot_box
      {
        \UseTblrTemplate { middlefoot } { default }
      }
    \__tblr_build_table_foot_aux:Nn \l__tblr_table_lastfoot_box
      {
        \UseTblrTemplate { lastfoot } { default }
      }
  }

\bool_new:N \l__tblr_table_no_title_bool
\bool_new:N \l__tblr_table_no_entry_bool
\bool_new:N \l__tblr_table_no_label_bool
\tl_const:Nn \cTblrNoneTl { none }

\cs_new_protected:Npn \__tblr_set_table_label_entry:
  {
    \tl_set:Ne \lTblrCaptionTl { \InsertTblrText { caption } }
    \tl_set:Ne \lTblrEntryTl { \InsertTblrText { entry } }
    \tl_set:Ne \lTblrLabelTl { \InsertTblrText { label } }
    \bool_set:Nn \l__tblr_table_no_title_bool
      { \tl_if_empty_p:N \lTblrCaptionTl }
    \bool_set:Nn \l__tblr_table_no_entry_bool
      { \tl_if_eq_p:NN \lTblrEntryTl \cTblrNoneTl }
    \bool_set:Nn \l__tblr_table_no_label_bool
      { \tl_if_eq_p:NN \lTblrLabelTl \cTblrNoneTl }
    \bool_if:NT \l__tblr_table_no_title_bool
      {
        \SetTblrTemplate { conthead-pre } { empty }
      }
    \bool_if:NT \l__tblr_table_no_label_bool
      {
        \SetTblrTemplate { caption-tag }{ empty }
        \SetTblrTemplate { caption-sep }{ empty }
      }
  }

\cs_new_protected:Npn \__tblr_build_tall_table_head_foot:
  {
    \__tblr_get_table_width:
    \__tblr_set_table_label_entry:
    \__tblr_build_table_head_aux:Nn \l__tblr_table_firsthead_box
      {
        \__tblr_build_table_label_entry:
        \UseTblrTemplate { firsthead } { default }
      }
    \__tblr_build_table_foot_aux:Nn
      \l__tblr_table_lastfoot_box  { \UseTblrTemplate { lastfoot } { default } }
  }

\tl_new:N \lTblrCaptionTl
\tl_new:N \lTblrEntryTl
\tl_new:N \lTblrLabelTl
\clist_new:N \lTblrRefMoreClist

\cs_new_protected:Npn \__tblr_build_table_label_entry:
  {
    \bool_if:NF \l__tblr_table_no_label_bool
      {
        \refstepcounter { table }
        \tl_if_empty:NF \lTblrLabelTl
          {
            \clist_map_inline:Nn \lTblrRefMoreClist
              { \ExpTblrTemplate { caption-ref } { ##1 } }
            \exp_args:NV \label \lTblrLabelTl
          }
      }
    %% We put caption-lot code at last, so that a user can modify \lTblrEntryTl
    %% in a caption-label template. For example, a user may want to use
    %% short caption in nameref, but at the same time not to add LoT entry.
    \bool_if:NF \l__tblr_table_no_entry_bool
      { \UseTblrTemplate { caption-lot } { default } }
  }

\cs_new_protected:Npn \__tblr_build_table_head_aux:Nn #1 #2
  {
    \vbox_set:Nn #1
      {
        \hsize = \lTblrTableWidthDim
        \TblrParboxRestore % it will set \linewidth = \hsize
        \vbox_set:Nn \l_tmpa_box {#2}
        \box_use:N \l_tmpa_box
        \dim_compare:nNnT
          { \box_ht:N \l_tmpa_box + \box_dp:N \l_tmpa_box } > { 0pt }
          { \skip_vertical:n { \__tblr_spec_item:nn { outer } { headsep } } }
      }
  }

\cs_new_protected:Npn \__tblr_build_table_foot_aux:Nn #1 #2
  {
    \vbox_set:Nn #1
      {
        \hsize = \lTblrTableWidthDim
        \TblrParboxRestore % it will set \linewidth = \hsize
        \vbox_set:Nn \l_tmpb_box {#2}
        \dim_compare:nNnT
          { \box_ht:N \l_tmpb_box + \box_dp:N \l_tmpb_box } > { 0pt }
          { \skip_vertical:n { \__tblr_spec_item:nn { outer } { footsep } } }
        \box_use:N \l_tmpb_box
      }
  }

\tl_new:N \lTblrPortraitTypeTl

\cs_new_protected:Npn \__tblr_build_whole:
  {
    \__tblr_hook_use:n { table/before }
    \tl_set:Ne \lTblrPortraitTypeTl
      { \__tblr_spec_item:nn { outer } { portrait } }
    \tl_if_eq:NnTF \lTblrPortraitTypeTl { long }
      {
        \__tblr_build_long_table:e { \__tblr_spec_item:nn { outer } { halign } }
      }
      {
        \tl_if_eq:NnTF \lTblrPortraitTypeTl { tall }
          {
            \__tblr_build_tall_table:e
              { \__tblr_spec_item:nn { outer } { baseline } }
          }
          {
            \tl_set:Nn \lTblrPortraitTypeTl { short }
            \__tblr_build_short_table:e
              { \__tblr_spec_item:nn { outer } { baseline } }
          }
      }
    \__tblr_hook_use:n { table/after }
  }

\dim_new:N \l__tblr_remain_height_dim
\int_new:N \l__tblr_long_from_int
\int_new:N \l__tblr_long_to_int
\int_new:N \l__tblr_curr_i_int
\int_new:N \l__tblr_prev_i_int
\int_new:N \lTblrTablePageInt
\bool_new:N \l__tblr_page_break_curr_bool
\bool_new:N \l__tblr_page_break_prev_bool

%% #1: table alignment
%% For long table, we need to leave hmode first to get correct \pagetotal
%% Also remove topskip and presep if we are at the beginning of the page
\cs_new_protected:Npn \__tblr_build_long_table:n #1
  {
    \LogTblrTracing { page }
    \par
    \skip_zero:N \parskip % see issue #203
    \LogTblrTracing { page }
    \dim_compare:nNnTF { \pagegoal } = { \maxdimen }
      { \hbox{}\kern-\topskip\nobreak }
      { \skip_vertical:n { \__tblr_spec_item:nn { outer } { presep } } }
    \LogTblrTracing { page }
    \nointerlineskip
    \mode_leave_vertical: % enter horizontal mode to update \pagetotal
    \LogTblrTracing { page }
    \hrule height ~ 0pt
    \nobreak % prevent page break after \hrule (see issue #42)
    \LogTblrTracing { page }
    \int_set:Nn \lTblrTablePageInt {1}
    \__tblr_build_head_foot:
    \dim_set:Nn \l__tblr_remain_height_dim
      { \pagegoal - \pagetotal - \l__tblr_row_head_foot_dim }
    \int_set:Nn \l__tblr_long_from_int { \lTblrRowHeadInt + 1 }
    \int_set:Nn \l__tblr_long_to_int { \c@rowcount - \lTblrRowFootInt }
    \int_set:Nn \l__tblr_curr_i_int { \l__tblr_long_from_int - 1 }
    \int_do_while:nNnn { \l__tblr_curr_i_int } < { \l__tblr_long_to_int }
      {
        \int_set_eq:NN \l__tblr_prev_i_int \l__tblr_curr_i_int
        \__tblr_get_next_table_rows:NNNN
          \l__tblr_long_to_int \l__tblr_curr_i_int
          \l_tmpa_dim \l__tblr_page_break_curr_bool
        \__tblr_check_table_page_break:NNN
          \l__tblr_remain_height_dim \l_tmpa_dim \l__tblr_page_break_prev_bool
        \__tblr_do_if_tracing:nn { page } { \int_log:N \l__tblr_curr_i_int }
        \bool_if:NTF \l__tblr_page_break_prev_bool
          {
            \int_compare:nNnTF
              { \l__tblr_long_from_int } > { \l__tblr_prev_i_int }
              {
                % See issue #42: if longtblr starts at the bottom of a page,
                % \pagetotal maybe exceed \pagegoal after adding presep,
                % or after adding rowhead or rowfoot of the table.
                % In these cases, we will not typeset table in this page,
                % but rather force a page break.
                \group_begin:
                  \dim_set:Nn \l_tmpb_dim
                    {
                      % Enough to overfill the page (including shrink).
                      \pagegoal - \pagetotal + \l_tmpa_dim
                      + \__tblr_box_height:N \l__tblr_table_firsthead_box
                      + \__tblr_box_height:N \l__tblr_table_firstfoot_box
                    }
                  \skip_vertical:n { \l_tmpb_dim }
                  \tex_penalty:D 9999
                  \skip_vertical:n { -\l_tmpb_dim }
                \group_end:
              }
              {
                \__tblr_build_page_table:nee {#1}
                  { \int_use:N \l__tblr_long_from_int }
                  { \int_use:N \l__tblr_prev_i_int }
                \int_incr:N \lTblrTablePageInt
                \int_set:Nn \l__tblr_long_from_int { \l__tblr_prev_i_int + 1 }
                \TblrNewPage
              }
            \hbox{}\kern-\topskip\nobreak
            \noindent
            \LogTblrTracing { page }
            \dim_set:Nn \l__tblr_remain_height_dim
              { \pagegoal - \pagetotal - \l__tblr_row_head_foot_dim - \l_tmpa_dim }
          }
          {
            \bool_if:NTF \l__tblr_page_break_curr_bool
              {
                \__tblr_build_page_table:nee {#1}
                  { \int_use:N \l__tblr_long_from_int }
                  { \int_use:N \l__tblr_curr_i_int }
                \int_incr:N \lTblrTablePageInt
                \TblrNewPage
                \hbox{}\kern-\topskip\nobreak
                \noindent
                \LogTblrTracing { page }
                \dim_set:Nn \l__tblr_remain_height_dim
                  { \pagegoal - \pagetotal - \l__tblr_row_head_foot_dim }
                \int_set:Nn \l__tblr_long_from_int { \l__tblr_curr_i_int + 1 }
              }
              { \dim_add:Nn \l__tblr_remain_height_dim { -\l_tmpa_dim } }
          }
      }
    \int_compare:nNnTF { \lTblrTablePageInt } = {1}
      {
        \box_set_eq:NN \l__tblr_table_head_box \l__tblr_table_firsthead_box
        \box_set_eq:NN \l__tblr_table_foot_box \l__tblr_table_lastfoot_box
      }
      {
        \box_set_eq:NN \l__tblr_table_head_box \l__tblr_table_lasthead_box
        \box_set_eq:NN \l__tblr_table_foot_box \l__tblr_table_lastfoot_box
      }
    \__tblr_build_page_table:nee {#1}
      { \int_use:N \l__tblr_long_from_int }
      { \int_use:N \l__tblr_long_to_int }
    \skip_vertical:n { \__tblr_spec_item:nn { outer } { postsep } }
    % In the past we used "\hrule height ~ 0pt" to get strict postsep,
    % but the postsep was not discarded when page breaks, see issue #39.
    % Therefore we use \nointerlineskip here.
    \nointerlineskip
  }
\cs_generate_variant:Nn \__tblr_build_long_table:n { e }

%% #1: int with index of the last row; #2: int with index of current row;
%% #3: row dimension; #4: break page or not.
\cs_new_protected:Npn \__tblr_get_next_table_rows:NNNN #1 #2 #3 #4
  {
    \bool_set_true:N \l_tmpa_bool
    \dim_zero:N #3
    \bool_set_false:N #4
    \bool_while_do:Nn \l_tmpa_bool
      {
        \int_incr:N #2
        \dim_add:Nn #3
          {
            \__tblr_data_item:nen { row } { \int_use:N #2 } { abovesep }
            +
            \__tblr_data_item:nen { row } { \int_use:N #2 } { @row-height }
            +
            \__tblr_data_item:nen { row } { \int_use:N #2 } { belowsep }
            +
            \__tblr_spec_item:ne { hline }
              { [ \int_eval:n { #2 + 1 } ] / @hline-height }
          }
        \int_compare:nNnTF {#2} < {#1}
          {
            \tl_set:Ne \l__tblr_b_tl
              {
                \__tblr_spec_item:ne { hline }
                  { [ \int_eval:n { #2 + 1 } ] / @pagebreak }
              }
            % Note that \l__tblr_b_tl may be empty
            \int_compare:nNnTF { \l__tblr_b_tl + 0 } < { 0 }
              { \bool_set_true:N \l_tmpa_bool }
              {
                \bool_set_false:N \l_tmpa_bool
                \int_compare:nNnT { \l__tblr_b_tl + 0 } > { 0 }
                  { \bool_set_true:N #4 }
              }
          }
          { \bool_set_false:N \l_tmpa_bool }
      }
  }

\box_new:N \l__tblr_table_head_box
\box_new:N \l__tblr_table_foot_box
\dim_new:N \l__tblr_table_head_foot_dim
\dim_new:N \l__tblr_table_head_body_foot_dim

%% #1: remain dimension; #2: row dimension; #3: break page or not
\cs_new_protected:Npn \__tblr_check_table_page_break:NNN #1 #2 #3
  {
    \int_compare:nNnTF { \lTblrTablePageInt } = {1}
      {
        \dim_set:Nn \l__tblr_table_head_body_foot_dim
          {
            \__tblr_box_height:N \l__tblr_table_firsthead_box
              + #2 + \__tblr_box_height:N \l__tblr_table_firstfoot_box
          }
        \box_set_eq:NN \l__tblr_table_head_box \l__tblr_table_firsthead_box
        \dim_compare:nNnTF
          { \l__tblr_table_head_body_foot_dim } > {#1}
          {
            \bool_set_true:N #3
            \box_set_eq:NN \l__tblr_table_foot_box \l__tblr_table_firstfoot_box
          }
          { \bool_set_false:N #3 }
      }
      {
        \dim_set:Nn \l__tblr_table_head_body_foot_dim
          {
            \__tblr_box_height:N \l__tblr_table_middlehead_box
              + #2 + \__tblr_box_height:N \l__tblr_table_middlefoot_box
          }
        \box_set_eq:NN \l__tblr_table_head_box \l__tblr_table_middlehead_box
        \dim_compare:nNnTF
          { \l__tblr_table_head_body_foot_dim } > {#1}
          {
            \bool_set_true:N #3
            \box_set_eq:NN \l__tblr_table_foot_box \l__tblr_table_middlefoot_box
          }
          { \bool_set_false:N #3 }
      }
  }

\box_new:N \l__tblr_table_box
\int_new:N \lTblrRowFirstInt
\int_new:N \lTblrRowLastInt

%% #1: table alignment; #2: row from; #3: row to
\cs_new_protected:Npn \__tblr_build_page_table:nnn #1 #2 #3
  {
    \int_set:Nn \lTblrRowFirstInt {#2}
    \int_set:Nn \lTblrRowLastInt {#3}
    \__tblr_build_one_table:nnNN {#2} {#3} \c_false_bool \c_false_bool
    \vbox_set:Nn \l__tblr_table_box
      {
        \box_use:N \l__tblr_table_head_box
        \__tblr_cover_two_vboxes:NN \l__tblr_row_head_box \l__tblr_table_box
        \box_use:N \l__tblr_row_foot_box
        \hrule height ~ 0pt
        \box_use:N \l__tblr_table_foot_box
      }
    \__tblr_hook_use:n { private/output/before }
    \__tblr_halign_whole:Nn \l__tblr_table_box {#1}
    \__tblr_hook_use:n { private/output/after }
  }
\cs_generate_variant:Nn \__tblr_build_page_table:nnn { nee }

%% To solve the problem of missing hlines of long tables in some PDF readers,
%% We need to draw body rows before head rows (see issue #88).
\cs_new_protected:Npn \__tblr_cover_two_vboxes:NN #1 #2
  {
    \dim_set:Nn \l_tmpa_dim { \box_ht:N #1 + \box_dp:N #1 }
    \dim_set:Nn \l_tmpb_dim { \box_ht:N #2 + \box_dp:N #2 }
    \skip_vertical:N \l_tmpa_dim
    \hrule height ~ 0pt
    \box_use:N #2
    \skip_vertical:n { - \l_tmpa_dim - \l_tmpb_dim }
    \hrule height ~ 0pt
    \box_use:N #1
    \skip_vertical:N \l_tmpb_dim
    \hrule height ~ 0pt
  }

\cs_new_protected:Npn \__tblr_halign_whole:Nn #1 #2
  {
    \noindent
    \hbox_to_wd:nn { \linewidth }
      {
        \tl_if_eq:nnF {#2} {l} { \hfil }
        \box_use:N #1
        \tl_if_eq:nnF {#2} {r} { \hfil }
      }
  }

%% #1: table alignment
%% For tall table, we need to leave vmode first.
%% Since there may be \centering in table environment,
%% We use \raggedright to reset alignment for table head/foot.
\cs_new_protected:Npn \__tblr_build_tall_table:n #1
  {
    \mode_leave_vertical:
    \__tblr_build_tall_table_head_foot:
    \__tblr_build_one_table:nnNN {1} {\c@rowcount} \c_true_bool \c_true_bool
    \vbox_set:Nn \l__tblr_table_box
      {
        \box_use:N \l__tblr_table_firsthead_box
        \hrule height ~ 0pt
        \box_use:N \l__tblr_table_box
        \hrule height ~ 0pt
        \box_use:N \l__tblr_table_lastfoot_box
      }
    \__tblr_hook_use:n { private/output/before }
    \__tblr_valign_whole:Nn \l__tblr_table_box {#1}
    \__tblr_hook_use:n { private/output/after }
  }
\cs_generate_variant:Nn \__tblr_build_tall_table:n { e }

%% #1: table alignment
%% For short table, we need to leave vmode first
\cs_new_protected:Npn \__tblr_build_short_table:n #1
  {
    \mode_leave_vertical:
    \__tblr_build_one_table:nnNN {1} {\c@rowcount} \c_true_bool \c_true_bool
    \__tblr_hook_use:n { private/output/before }
    \__tblr_valign_whole:Nn \l__tblr_table_box {#1}
    \__tblr_hook_use:n { private/output/after }
  }
\cs_generate_variant:Nn \__tblr_build_short_table:n { e }

\box_new:N \l__tblr_table_hlines_box
\box_new:N \l__tblr_hline_box
\box_new:N \l__tblr_row_box

%% #1: row from; #2: row to
%% #3: whether build first hline or not; #4: whether build last hline or not
%% To fix disappeared hlines with colorful tables in Adobe Reader (see #76),
%% we collect all hlines and draw them at the end of the table.
\cs_new_protected:Npn \__tblr_build_one_table:nnNN #1 #2 #3 #4
  {
    \box_clear:N \l__tblr_table_hlines_box
    \tblr_vbox_set:Nn \l__tblr_table_box
      {
        \int_step_variable:nnNn {#1} {#2} \l__tblr_i_tl
          {
            \bool_lazy_or:nnT
              { \int_compare_p:nNn { \l__tblr_i_tl } > {#1} }
              { \bool_if_p:N #3 }
              { \__tblr_put_one_hline:n { \__tblr_build_hline:V \l__tblr_i_tl } }
            \tblr_hrule_ht:n { 0pt } % remove lineskip between hlines and rows
            \__tblr_put_one_row:n { \__tblr_build_row:N \l__tblr_i_tl }
            \tblr_hrule_ht:n { 0pt }
          }
        \bool_if:NT #4
          {
            \__tblr_put_one_hline:n
              { \__tblr_build_hline:n { \int_eval:n {#2 + 1} } }
          }
        \skip_vertical:n
          {
            - \box_ht:N \l__tblr_table_hlines_box
            - \box_dp:N \l__tblr_table_hlines_box
          }
        \tblr_box_use:N \l__tblr_table_hlines_box
      }
  }

\cs_new_protected:Npn \__tblr_put_one_hline:n #1
  {
    \hbox_set:Nn \l__tblr_hline_box {#1}
    \skip_vertical:n { \box_ht:N \l__tblr_hline_box + \box_dp:N \l__tblr_hline_box }
    \vbox_set:Nn \l__tblr_table_hlines_box
      {
        \vbox_unpack:N \l__tblr_table_hlines_box
        \box_use:N \l__tblr_hline_box
      }
  }

\cs_new_protected:Npn \__tblr_put_one_row:n #1
  {
    \hbox_set:Nn \l__tblr_row_box {#1}
    \vbox_set:Nn \l__tblr_table_hlines_box
      {
        \vbox_unpack:N \l__tblr_table_hlines_box
        \skip_vertical:n
          { \box_ht:N \l__tblr_row_box + \box_dp:N \l__tblr_row_box }
      }
    \box_use:N \l__tblr_row_box
  }

%% #1: hline number
\cs_new_protected:Npn \__tblr_build_one_hline:n #1
  {
    \vbox_set:Nn \l__tblr_table_box { \hbox:n { \__tblr_build_hline:n { #1 } } }
  }

\tl_new:N \l__tblr_vbox_align_tl
\tl_const:Nn \c__tblr_vbox_t_tl {t}
\tl_const:Nn \c__tblr_vbox_T_tl {T}
\tl_const:Nn \c__tblr_vbox_m_tl {m}
\tl_const:Nn \c__tblr_vbox_M_tl {M}
\tl_const:Nn \c__tblr_vbox_c_tl {c}
\tl_const:Nn \c__tblr_vbox_b_tl {b}
\tl_const:Nn \c__tblr_vbox_B_tl {B}

\tl_new:N \l__tblr_delim_left_tl
\tl_new:N \l__tblr_delim_right_tl

\cs_new_protected:Npn \__tblr_valign_whole:Nn #1 #2
  {
    \group_begin:
    \tl_set:Ne \l__tblr_delim_left_tl
      { \__tblr_prop_item:nn { inner } { delim-left } }
    \tl_set:Ne \l__tblr_delim_right_tl
      { \__tblr_prop_item:nn { inner } { delim-right } }
    \tl_set:Nn \l__tblr_vbox_align_tl {#2}
    \dim_set:Nn \l__tblr_t_dim { \box_ht:N #1 + \box_dp:N #1 }
    \tl_case:NnF \l__tblr_vbox_align_tl
      {
        \c__tblr_vbox_m_tl
          { \__tblr_valign_whole_middle:N #1 }
        \c__tblr_vbox_c_tl
          { \__tblr_valign_whole_middle:N #1 }
        \c__tblr_vbox_M_tl
          { \__tblr_valign_whole_middle_row_or_border:N #1 }
        \c__tblr_vbox_t_tl
          { \__tblr_valign_whole_top:N #1 }
        \c__tblr_vbox_T_tl
          {
            \tl_set:Nn \l__tblr_vbox_align_tl {1}
            \__tblr_valign_whole_at_row_from_above:N #1
          }
        \c__tblr_vbox_b_tl
          { \__tblr_valign_whole_bottom:N #1 }
        \c__tblr_vbox_B_tl
          {
            \tl_set:Ne \l__tblr_vbox_align_tl { \int_use:N \c@rowcount }
            \__tblr_valign_whole_at_row_from_below:N #1
          }
      }
      {
        \__tblr_if_positive_value:VTF \l__tblr_vbox_align_tl
          { \__tblr_valign_whole_at_row:N #1 }
          {
            \__tblr_if_negative_value:VTF \l__tblr_vbox_align_tl
              { \__tblr_valign_whole_at_border:N #1 }
              { \__tblr_valign_whole_middle:N #1 }
          }
      }
    %% we have done the job when valign is m or c
    \box_if_empty:NF #1 { \__tblr_add_delimiters_to_box:N #1 }
    \group_end:
  }

%% We use the idea of delarray package to shift table box
%% when there are delimiters around the table
\cs_new_protected:Npn \__tblr_add_delimiters_to_box:N #1
  {
    \tl_if_empty:NTF \l__tblr_delim_left_tl
      { \box_use_drop:N #1 }
      {
        \box_move_down:nn
          {
            ( \box_dp:N #1 - \box_ht:N #1 ) / 2
            + \tex_fontdimen:D 22 \tex_textfont:D 2
          }
          { \__tblr_get_vcenter_box:N #1 }
      }
  }

\cs_new_protected:Npn \__tblr_get_vcenter_box:N #1
  {
    \hbox:n
      {
        $ \m@th \l__tblr_delim_left_tl
        \tex_vcenter:D { \vbox_unpack_drop:N #1 }
        \l__tblr_delim_right_tl $
      }
  }

\cs_new_protected:Npn \__tblr_valign_whole_middle:N #1
  {
    \__tblr_get_vcenter_box:N #1
  }

\cs_new_protected:Npn \__tblr_valign_whole_top:N #1
  {
    \dim_set:Nn \l__tblr_h_dim { \__tblr_valign_get_hline_total:n {1} }
    \dim_compare:nNnT \l__tblr_h_dim = { 0pt }
      { \dim_add:Nn \l__tblr_h_dim { \__tblr_valign_get_row_height:n {1} } }
    \box_set_ht:Nn #1 { \l__tblr_h_dim }
    \box_set_dp:Nn #1 { \l__tblr_t_dim - \l__tblr_h_dim }
  }

\cs_new_protected:Npn \__tblr_valign_whole_bottom:N #1
  {
    \dim_set:Nn \l__tblr_d_dim
      { \__tblr_valign_get_hline_total:n { \int_eval:n { \c@rowcount + 1 } } }
    \dim_compare:nNnTF \l__tblr_d_dim = { 0pt }
      {
        \dim_set:Nn \l__tblr_d_dim
          { \__tblr_valign_get_row_depth:n { \int_use:N \c@rowcount } }
      }
      { \dim_zero:N \l__tblr_d_dim }
    \box_set_ht:Nn #1 { \l__tblr_t_dim - \l__tblr_d_dim }
    \box_set_dp:Nn #1 { \l__tblr_d_dim }
  }

\cs_new_protected:Npn \__tblr_valign_whole_middle_row_or_border:N #1
  {
    \int_if_odd:nTF { \c@rowcount }
      {
        \tl_set:Ne \l__tblr_vbox_align_tl { \int_eval:n { (\c@rowcount + 1) / 2 } }
        \__tblr_valign_whole_at_row_from_above:N #1
      }
      {
        \tl_set:Ne \l__tblr_vbox_align_tl { \int_eval:n { \c@rowcount / 2 + 1 } }
        \__tblr_valign_whole_at_border_from_above:N #1
      }
  }

\cs_new_protected:Npn \__tblr_valign_whole_at_row:N #1
  {
    \int_compare:nNnTF { 2 * \l__tblr_vbox_align_tl } > { \c@rowcount }
      { \__tblr_valign_whole_at_row_from_below:N #1 }
      { \__tblr_valign_whole_at_row_from_above:N #1 }
  }

\cs_new_protected:Npn \__tblr_valign_whole_at_row_from_above:N #1
  {
    \dim_set:Nn \l__tblr_h_dim
      { \__tblr_valign_get_hline_total:n { \l__tblr_vbox_align_tl } }
    \dim_add:Nn \l__tblr_h_dim
      { \__tblr_valign_get_row_height:n { \l__tblr_vbox_align_tl } }
    \int_step_inline:nn { \l__tblr_vbox_align_tl - 1 }
      {
        \dim_add:Nn \l__tblr_h_dim { \__tblr_valign_get_hline_total:n {##1} }
        \dim_add:Nn \l__tblr_h_dim { \__tblr_valign_get_row_total:n {##1} }
      }
    \box_set_ht:Nn #1 { \l__tblr_h_dim }
    \box_set_dp:Nn #1 { \l__tblr_t_dim - \l__tblr_h_dim }
  }

\cs_new_protected:Npn \__tblr_valign_whole_at_row_from_below:N #1
  {
    \dim_set:Nn \l__tblr_d_dim
      { \__tblr_valign_get_hline_total:n { \int_eval:n {\c@rowcount + 1} } }
    \dim_add:Nn \l__tblr_d_dim
      { \__tblr_valign_get_row_depth:n { \l__tblr_vbox_align_tl } }
    \int_step_inline:nnn { \l__tblr_vbox_align_tl + 1 } { \c@rowcount }
      {
        \dim_add:Nn \l__tblr_d_dim { \__tblr_valign_get_hline_total:n {##1} }
        \dim_add:Nn \l__tblr_d_dim { \__tblr_valign_get_row_total:n {##1} }
      }
    \box_set_dp:Nn #1 { \l__tblr_d_dim }
    \box_set_ht:Nn #1 { \l__tblr_t_dim - \l__tblr_d_dim }
  }

\cs_new_protected:Npn \__tblr_valign_whole_at_border:N #1
  {
    \tl_set:Ne \l__tblr_vbox_align_tl { \int_eval:n { - \l__tblr_vbox_align_tl } }
    \int_compare:nNnTF { 2 * \l__tblr_vbox_align_tl - 2 } > { \c@rowcount }
      { \__tblr_valign_whole_at_border_from_below:N #1 }
      { \__tblr_valign_whole_at_border_from_above:N #1 }
  }

\cs_new_protected:Npn \__tblr_valign_whole_at_border_from_above:N #1
  {
    \dim_set:Nn \l__tblr_h_dim
      { \__tblr_valign_get_hline_total:n { \l__tblr_vbox_align_tl } }
    \int_step_inline:nn { \l__tblr_vbox_align_tl - 1 }
      {
        \dim_add:Nn \l__tblr_h_dim { \__tblr_valign_get_hline_total:n {##1} }
        \dim_add:Nn \l__tblr_h_dim { \__tblr_valign_get_row_total:n {##1} }
      }
    \box_set_ht:Nn #1 { \l__tblr_h_dim }
    \box_set_dp:Nn #1 { \l__tblr_t_dim - \l__tblr_h_dim }
  }

\cs_new_protected:Npn \__tblr_valign_whole_at_border_from_below:N #1
  {
    \dim_zero:N \l__tblr_d_dim
    \int_step_inline:nnn { \l__tblr_vbox_align_tl } { \c@rowcount }
      {
        \dim_add:Nn \l__tblr_d_dim { \__tblr_valign_get_row_total:n {##1} }
        \dim_add:Nn \l__tblr_d_dim
          { \__tblr_valign_get_hline_total:n { \int_eval:n { ##1 + 1 } } }
      }
    \box_set_dp:Nn #1 { \l__tblr_d_dim }
    \box_set_ht:Nn #1 { \l__tblr_t_dim - \l__tblr_d_dim }
  }

\cs_new_nopar:Npn \__tblr_valign_get_hline_total:n #1
  {
    \__tblr_spec_item:ne { hline } { [#1] / @hline-height }
  }

\cs_new_nopar:Npn \__tblr_valign_get_row_total:n #1
  {
    \__tblr_data_item:nen { row } {#1} { abovesep }
    +
    \__tblr_data_item:nen { row } {#1} { @row-height }
    +
    \__tblr_data_item:nen { row } {#1} { belowsep }
  }

\cs_new_nopar:Npn \__tblr_valign_get_row_height:n #1
  {
    \__tblr_data_item:nen { row } {#1} { abovesep }
    +
    ( \__tblr_data_item:nen { row } {#1} { @row-height }
      +
      \__tblr_data_item:nen { row } {#1} { @row-upper }
      -
      \__tblr_data_item:nen { row } {#1} { @row-lower }
    ) / 2
  }

\cs_new_nopar:Npn \__tblr_valign_get_row_depth:n #1
  {
    ( \__tblr_data_item:nen { row } {#1} { @row-height }
      -
      \__tblr_data_item:nen { row } {#1} { @row-upper }
      +
      \__tblr_data_item:nen { row } {#1} { @row-lower }
    ) / 2
    +
    \__tblr_data_item:nen { row } {#1} { belowsep }
  }

%%% --------------------------------------------------------
%%> \section{Build table components}
%%% --------------------------------------------------------

\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@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:Ne \l__tblr_n_tl
      { \__tblr_spec_item:ne { hline } { [#1] / @hline-count } }
    \tl_set:Ne \l__tblr_o_tl
      { \__tblr_spec_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
      {
        \int_compare:nNnT { \l__tblr_n_tl } > {0}
          { \__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 }
  }

\tl_new:N \lTblrDefaultHruleColorTl

%% #1: row number, #2: column number
\cs_new_protected:Npn \__tblr_build_hline_segment_real:nn #1 #2
  {
    \tl_set:Ne \l__tblr_s_tl
      { \__tblr_prop_item:ne { inner } { 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:Ne \l__tblr_h_tl
              { \__tblr_spec_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 }
              {
                \__tblr_get_hline_left_right_skips:nnn {#1} {#2} {##1}
                \skip_horizontal:N \l__tblr_hline_leftskip_dim
                \tl_set:Ne \l__tblr_f_tl
                  { \__tblr_spec_item:ne { hline } { [#1][#2](##1) / fg } }
                \tl_if_empty:NTF \l__tblr_f_tl
                  {
                    \tl_if_empty:NF \lTblrDefaultHruleColorTl
                      { \color { \lTblrDefaultHruleColorTl } }
                  }
                  { \color { \l__tblr_f_tl } }
                \__tblr_get_hline_segment_child:nnn {#1} {#2} {##1}
                \skip_horizontal:N \l__tblr_hline_rightskip_dim
              }
            \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_spec_item:ne { vline } { [\int_eval:n {#1 + 1}] / @vline-width } }
    \dim_set:Nn #2
      {
        \__tblr_spec_item:ne { vline } { [#1] / @vline-width }
        +
        \__tblr_data_item:nen { column } {#1} { leftsep }
        +
        \__tblr_data_item:nen { column } {#1} { @col-width }
        +
        \__tblr_data_item:nen { column } {#1} { rightsep }
        +
        #3
      }
  }

\dim_new:N \l__tblr_hline_leftskip_dim
\dim_new:N \l__tblr_hline_rightskip_dim

%% Calculate left and right skips from leftpos and rightpos specifications
%% #1: row number; #2: column number; #3: hline index;
\cs_new_protected:Npn \__tblr_get_hline_left_right_skips:nnn #1 #2 #3
  {
    \tl_set:Ne \l__tblr_hline_leftpos_tl
      { \__tblr_spec_item:ne { hline } { [#1][#2](#3) / leftpos } }
    \tl_if_empty:NT \l__tblr_hline_leftpos_tl
      { \tl_set:Nn \l__tblr_hline_leftpos_tl {1} } % default position
    \tl_set:Ne \l__tblr_hline_rightpos_tl
      { \__tblr_spec_item:ne { hline } { [#1][#2](#3) / rightpos } }
    \tl_if_empty:NT \l__tblr_hline_rightpos_tl
      { \tl_set:Nn \l__tblr_hline_rightpos_tl {1} } % default position
    \fp_compare:nNnT { \l__tblr_hline_leftpos_tl } < {1}
      {
        \dim_set:Nn \l_tmpa_dim
          { \__tblr_spec_item:ne { vline } { [#2] / @vline-width } }
        \dim_set:Nn \l_tmpb_dim
          { \__tblr_data_item:nen { column } {#2} { leftsep } }
        \fp_compare:nNnTF { \l__tblr_hline_leftpos_tl } < {0}
          {
            \dim_set:Nn \l__tblr_hline_leftskip_dim
              { \l_tmpa_dim - \l__tblr_hline_leftpos_tl \l_tmpb_dim }
          }
          {
            \dim_set:Nn \l__tblr_hline_leftskip_dim
              { \l_tmpa_dim - \l__tblr_hline_leftpos_tl \l_tmpa_dim }
          }
      }
    \fp_compare:nNnT { \l__tblr_hline_rightpos_tl } < {1}
      {
        \dim_set:Nn \l_tmpa_dim
          {
            \__tblr_spec_item:ne { vline }
              { [\int_eval:n { #2 + 1 }] / @vline-width }
          }
        \dim_set:Nn \l_tmpb_dim
          { \__tblr_data_item:nen { column } {#2} { rightsep } }
        \fp_compare:nNnTF { \l__tblr_hline_rightpos_tl } < {0}
          {
            \dim_set:Nn \l__tblr_hline_rightskip_dim
              { \l_tmpa_dim - \l__tblr_hline_rightpos_tl \l_tmpb_dim }
          }
          {
            \dim_set:Nn \l__tblr_hline_rightskip_dim
              { \l_tmpa_dim - \l__tblr_hline_rightpos_tl \l_tmpa_dim }
          }
      }
  }

\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
\box_new:N \l__tblr_row_vlines_box
\box_new:N \l__tblr_vline_box
\box_new:N \l__tblr_cell_box

%% Build current row, #1: row number
%% To fix disappeared vlines with colorful tables in Adobe Reader (see #76),
%% we collect all vlines and draw them at the end of the row.
\cs_new_protected:Npn \__tblr_build_row:N #1
  {
    \int_set:Nn \c@rownum {#1}
    \__tblr_update_rowsep_registers:
    \__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
    \__tblr_hook_use:n { row/before }
    \tblr_vrule_wd_ht_dp:nnn {0pt} {\l__tblr_row_ht_dim} {\l__tblr_row_dp_dim}
    \hbox_set:Nn \l__tblr_row_vlines_box
      {
        \tblr_vrule_wd_ht_dp:nnn {0pt} {\l__tblr_row_ht_dim} {\l__tblr_row_dp_dim}
      }
    \int_step_variable:nNn { \c@colcount } \l__tblr_j_tl
      {
        \__tblr_put_one_vline:n
          { \__tblr_build_vline_segment:nn {#1} { \l__tblr_j_tl } }
        \__tblr_put_one_cell:n { \__tblr_build_cell:NN #1 \l__tblr_j_tl }
      }
    \__tblr_put_one_vline:n
      { \__tblr_build_vline_segment:nn {#1} { \int_eval:n {\c@colcount + 1} } }
    \skip_horizontal:n { - \box_wd:N \l__tblr_row_vlines_box }
    \box_use:N \l__tblr_row_vlines_box
    \__tblr_hook_use:n { row/after }
  }

%% 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__tblr_row_upper_dim
\dim_new:N \l__tblr_row_lower_dim
\dim_new:N \l__tblr_row_vpace_dim

\cs_new_protected:Npn \__tblr_get_row_inner_height_depth:nNNNN #1 #2 #3 #4 #5
  {
    \dim_set:Nn #4
      { \__tblr_data_item:nen { row } {#1} { abovesep } }
    \dim_set:Nn #5
      { \__tblr_data_item:nen { row } {#1} { belowsep } }
    \dim_set:Nn \l__tblr_row_upper_dim
      {  \__tblr_data_item:nen { row } {#1} { @row-upper } }
    \dim_set:Nn \l__tblr_row_lower_dim
      {  \__tblr_data_item:nen { row } {#1} { @row-lower } }
    \dim_set:Nn \l__tblr_row_vpace_dim
      {
        ( \__tblr_data_item:nen { row } {#1} { @row-height }
          - \l__tblr_row_upper_dim - \l__tblr_row_lower_dim ) / 2
      }
    \dim_set:Nn #2 { #4 + \l__tblr_row_vpace_dim + \l__tblr_row_upper_dim }
    \dim_set:Nn #3 { \l__tblr_row_lower_dim + \l__tblr_row_vpace_dim + #5 }
  }
\cs_generate_variant:Nn \__tblr_get_row_inner_height_depth:nNNNN { V }

\cs_new_protected:Npn \__tblr_put_one_vline:n #1
  {
    \hbox_set:Nn \l__tblr_vline_box {#1}
    \skip_horizontal:n { \box_wd:N \l__tblr_vline_box }
    \hbox_set:Nn \l__tblr_row_vlines_box
      {
        \hbox_unpack:N \l__tblr_row_vlines_box
        \box_use:N \l__tblr_vline_box
      }
  }

\cs_new_protected:Npn \__tblr_put_one_cell:n #1
  {
    \hbox_set:Nn \l__tblr_cell_box {#1}
    \hbox_set:Nn \l__tblr_row_vlines_box
      {
        \hbox_unpack:N \l__tblr_row_vlines_box
        \skip_horizontal:n { \box_wd:N \l__tblr_cell_box }
      }
    \box_use:N \l__tblr_cell_box
  }

%% #1: row number, #2: column number
\cs_new_protected:Npn \__tblr_build_vline_segment:nn #1 #2
  {
    \tl_set:Ne \l__tblr_n_tl
      { \__tblr_spec_item:ne { vline } { [#2] / @vline-count } }
    \tl_set:Ne \l__tblr_o_tl
      { \__tblr_spec_item:ne { vline } { [#1][#2] / omit } }
    \tl_if_empty:NTF \l__tblr_o_tl
      {
        \int_compare:nNnT { \l__tblr_n_tl } > {0}
          { \__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:Ne \l__tblr_w_tl
      { \__tblr_spec_item:ne { vline } { [#2] / @vline-width } }
    \skip_horizontal:N \l__tblr_w_tl
  }

\tl_new:N \lTblrDefaultVruleColorTl

%% #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:Ne \l__tblr_s_tl
      { \__tblr_prop_item:ne { inner } { rulesep } }
    \hbox_set:Nn \l__tblr_a_box
      {
        \int_step_inline:nn { \l__tblr_n_tl }
          {
            \tl_set:Ne \l__tblr_w_tl
              { \__tblr_spec_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:Ne \l__tblr_f_tl
                  { \__tblr_spec_item:ne { vline } { [#1][#2](##1) / fg } }
                \tl_if_empty:NTF \l__tblr_f_tl
                  {
                    \tl_if_empty:NF \lTblrDefaultVruleColorTl
                      { \color { \lTblrDefaultVruleColorTl } }
                  }
                  { \color { \l__tblr_f_tl } }
                \__tblr_get_vline_above_below_skips:nnn {#1} {#2} {##1}
                \skip_vertical:N \l__tblr_vline_aboveskip_dim
                \__tblr_get_vline_segment_child:nnnee {#1} {#2} {##1}
                  { \dim_eval:n { \l__tblr_row_ht_dim } }
                  { \dim_eval:n { \l__tblr_row_dp_dim } }
                \skip_vertical:N \l__tblr_vline_belowskip_dim
              }
            \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
  }

\dim_new:N \l__tblr_vline_aboveskip_dim
\dim_new:N \l__tblr_vline_belowskip_dim

%% Calculate above and below skips from abovepos and belowpos specifications
%% #1: row number; #2: column number; #3: vline index;
\cs_new_protected:Npn \__tblr_get_vline_above_below_skips:nnn #1 #2 #3
  {
    \tl_set:Ne \l__tblr_vline_abovepos_tl
      { \__tblr_spec_item:ne { vline } { [#1][#2](#3) / abovepos } }
    \tl_if_empty:NT \l__tblr_vline_abovepos_tl
      {
        \tl_set:Nn \l__tblr_vline_abovepos_tl {0} % default position
      }
    \fp_compare:nNnF { \l__tblr_vline_abovepos_tl } = {0}
      {
        \dim_set:Nn \l_tmpa_dim
          { \__tblr_spec_item:ne { hline } { [#1] / @hline-height } }
        \fp_compare:nNnTF { \l__tblr_vline_abovepos_tl } < {0}
          {
            \dim_set:Nn \l__tblr_vline_aboveskip_dim
              { - \l__tblr_vline_abovepos_tl \l__tblr_row_abovesep_dim }
          }
          {
            \dim_set:Nn \l__tblr_vline_aboveskip_dim
              { - \l__tblr_vline_abovepos_tl \l_tmpa_dim }
          }
      }
    %% To join two vline segment above and below a cline,
    %% we choose to extend every vline downwards a little (#55, #272).
    \tl_set:Ne \l__tblr_vline_belowpos_tl
      { \__tblr_spec_item:ne { vline } { [#1][#2](#3) / belowpos } }
    \tl_if_empty:NTF \l__tblr_vline_belowpos_tl
      {
        \dim_set:Nn \l__tblr_vline_belowskip_dim
          {
            - \__tblr_spec_item:ne { hline }
                { [\int_eval:n { #1 + 1 }](1) / @hline-height }
            + 0pt
          }
      }
      {
        \dim_set:Nn \l_tmpa_dim
          {
            \__tblr_spec_item:ne { hline }
              { [\int_eval:n { #1 + 1 }] / @hline-height }
          }
        \fp_compare:nNnTF { \l__tblr_vline_belowpos_tl } < {0}
          {
            \dim_set:Nn \l__tblr_vline_belowskip_dim
              { - \l__tblr_vline_belowpos_tl \l__tblr_row_belowsep_dim }
          }
          {
            \dim_set:Nn \l__tblr_vline_belowskip_dim
              { - \l__tblr_vline_belowpos_tl \l_tmpa_dim }
          }
      }
  }

%% These public variables are updated by default before building a cell
\int_new:N \lTblrCellRowSpanInt
\int_new:N \lTblrCellColSpanInt
\tl_new:N \lTblrCellBackgroundTl
\bool_new:N \lTblrCellOmittedBool

\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@colnum {#2}
    \__tblr_update_colsep_registers:
    \group_begin:
    \tl_set:Ne \l__tblr_w_tl
      { \__tblr_data_item:nen { column } {#2} { @col-width } }
    \tl_set:Ne \l__tblr_h_tl
      { \__tblr_data_item:nen { row } {#1} { @row-height } }
    \tl_set:Ne \l__tblr_x_tl
      { \__tblr_data_item:nen { column } {#2} { leftsep} }
    \tl_set:Ne \l__tblr_y_tl
      { \__tblr_data_item:nen { column } {#2} { rightsep } }
    \int_set:Nn \lTblrCellColSpanInt
      { \__tblr_data_item:neen { cell } {#1} {#2} { colspan } }
    \int_compare:nNnTF { \lTblrCellColSpanInt } < {2}
      { \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
      }
    \int_set:Nn \lTblrCellRowSpanInt
      { \__tblr_data_item:neen { cell } {#1} {#2} { rowspan } }
    \int_compare:nNnTF { \lTblrCellRowSpanInt } < {2}
      { \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:
  }

%% These public variables are updated by html library before building a cell
\tl_new:N \lTblrCellAboveBorderStyleTl
\dim_new:N \lTblrCellAboveBorderWidthDim
\tl_new:N \lTblrCellAboveBorderColorTl
\tl_new:N \lTblrCellBelowBorderStyleTl
\dim_new:N \lTblrCellBelowBorderWidthDim
\tl_new:N \lTblrCellBelowBorderColorTl
\tl_new:N \lTblrCellLeftBorderStyleTl
\dim_new:N \lTblrCellLeftBorderWidthDim
\tl_new:N \lTblrCellLeftBorderColorTl
\tl_new:N \lTblrCellRightBorderStyleTl
\dim_new:N \lTblrCellRightBorderWidthDim
\tl_new:N \lTblrCellRightBorderColorTl

%% #1: row number in tl; #2: column number in tl
%% This function is called only when html library is loaded.
%% The properties can be used by tagpdf, tex4ht and lwarp packages
\cs_new_protected:Npn \__tblr_expose_cell_properties:NN #1 #2
  {
    \__tblr_expose_cell_border:NNnn #1 #2 { hline } { Above }
    \tl_set:Ne \l_tmpa_tl { \int_eval:n { #1 + \lTblrCellRowSpanInt } }
    \__tblr_expose_cell_border:NNnn \l_tmpa_tl #2 { hline } { Below }
    \__tblr_expose_cell_border:NNnn #1 #2 { vline } { Left }
    \tl_set:Ne \l_tmpb_tl { \int_eval:n { #2 + \lTblrCellColSpanInt } }
    \__tblr_expose_cell_border:NNnn #1 \l_tmpb_tl { vline } { Right }
  }

\tl_new:N \l__tblr_dash_value_tl
\tl_new:N \l__tblr_width_value_tl
\tl_new:N \l__tblr_color_value_tl

%% #1: row number in tl; #2: column number in tl;
%% #3: hline or vline; #4: position of border (Above/Below/Left/Right).
\cs_new_protected:Npn \__tblr_expose_cell_border:NNnn #1 #2 #3 #4
  {
    %% get border style
    \tl_set:Ne \l__tblr_dash_value_tl %% may be empty
      { \__tblr_spec_item:ne { #3 } { [#1][#2](1) / @dash } }
    \tl_if_head_eq_meaning:VNTF \l__tblr_dash_value_tl \q__tblr_dash
      {
        \tl_set:ce { lTblrCell #4 BorderStyleTl }
          { \tl_tail:N \l__tblr_dash_value_tl }
        %% get border width
        \tl_set:Ne \l__tblr_width_value_tl
          { \__tblr_spec_item:ne { #3 } { [#1][#2](1) / wd } }
        \tl_if_empty:NTF \l__tblr_width_value_tl
          { \dim_set:cn { lTblrCell #4 BorderWidthDim } { 0.4pt } }
          {
            \dim_set:cn { lTblrCell #4 BorderWidthDim }
              { \l__tblr_width_value_tl }
          }
        %% get border color
        \tl_set:ce { lTblrCell #4 BorderColorTl }
          { \__tblr_spec_item:ne { #3 } { [#1][#2](1) / fg } }
      }
      {
        \tl_clear:c { lTblrCell #4 BorderStyleTl }
        \dim_set:cn { lTblrCell #4 BorderWidthDim } { 0pt }
        \tl_clear:c { lTblrCell #4 BorderColorTl }
      }
  }

\cs_new_protected:Npn \__tblr_build_cell_content:NN #1 #2
  {
    \bool_if:NT \l__tblr_html_variables_bool
      { \__tblr_expose_cell_properties:NN #1 #2 }
    \__tblr_hook_use:n { cell/before }
    \hbox_set_to_wd:Nnn \l__tblr_a_box { \l__tblr_cell_wd_dim }
      {
        \tl_if_eq:NnTF \g__tblr_cell_halign_tl {j}
          % cell width may be less than column width for j cells
          { \__tblr_get_cell_text:nn {#1} {#2} \hfil }
          {
            \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
                \int_compare:nNnT { \lTblrCellRowSpanInt } < {2}
                  {
                    \box_set_ht:Nn \l__tblr_a_box
                      { \__tblr_data_item:nen { row } {#1} { @row-upper } }
                    \box_set_dp:Nn \l__tblr_a_box
                      { \__tblr_data_item:nen { 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_data_item:nen { row } {#1} { @row-head } }
                \box_use:N \l__tblr_a_box
                \vfil
              }
            \c__tblr_valign_f_tl
              {
                \vfil
                \int_compare:nNnTF { \lTblrCellRowSpanInt } < {2}
                  {
                    \box_set_dp:Nn \l__tblr_a_box
                      { \__tblr_data_item:nen { row } {#1} { @row-foot } }
                  }
                  {
                    \box_set_dp:Nn \l__tblr_a_box
                      {
                        \__tblr_data_item:nen
                          { row }
                          { \int_eval:n { #1 + \lTblrCellRowSpanInt - 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 }
    \__tblr_hook_use:n { cell/after }
  }

\cs_new_protected:Npn \__tblr_build_cell_background:NN #1 #2
  {
    \bool_set:Nn \lTblrCellOmittedBool
      {
        \int_compare_p:nNn
          { \__tblr_data_item:neen { cell } {#1} {#2} { omit } } = {1}
      }
    \bool_if:NF \lTblrCellOmittedBool
      {
        \tl_set:Ne \lTblrCellBackgroundTl
          { \__tblr_data_item:neen { cell } {#1} {#2} { background } }
        \group_begin:
        \tl_if_empty:NF \lTblrCellBackgroundTl
          {
            \__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 }
              { \lTblrCellBackgroundTl }
          }
        \group_end:
      }
  }

%% #1: row number; #2: column number; #3 resulting dimension
\cs_new_protected:Npn \__tblr_get_cell_background_width:NNN #1 #2 #3
  {
    \int_compare:nNnTF { \lTblrCellColSpanInt } < {2}
      { \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
  {
    \int_compare:nNnTF { \lTblrCellRowSpanInt } < {2}
      { \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
%% But when vspan=even, 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_vertical_sizes:NNNNN #1 #2 #3 #4 #5
  {
    \dim_set:Nn #3
      { \__tblr_data_item:nen { row } {#1} { abovesep } }
    \dim_zero:N #4
    \dim_add:Nn #4
      { \prop_item:Ne \l__tblr_row_item_skip_size_prop { item[#1] } }
    \int_step_inline:nnn { #1 + 1 } { #1 + \lTblrCellRowSpanInt - 1 }
      {
        \dim_add:Nn #4
          {
            \prop_item:Ne \l__tblr_row_item_skip_size_prop { skip[##1] }
            +
            \prop_item:Ne \l__tblr_row_item_skip_size_prop { item[##1] }
          }
      }
    \dim_set:Nn #5
      {
        \__tblr_data_item:nen { row }
          { \int_eval:n { #1 + \lTblrCellRowSpanInt - 1 } } { belowsep }
      }
    %\tl_log:e { 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=even or 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_data_item:nen { column } {#2} { leftsep } }
    \dim_zero:N #4
    \dim_add:Nn #4
      { \prop_item:Ne \l__tblr_col_item_skip_size_prop { item[#2] } }
    \int_step_inline:nnn { #2 + 1 } { #2 + \lTblrCellColSpanInt - 1 }
      {
        \dim_add:Nn #4
          {
            \prop_item:Ne \l__tblr_col_item_skip_size_prop { skip[##1] }
            +
            \prop_item:Ne \l__tblr_col_item_skip_size_prop { item[##1] }
          }
      }
    \dim_set:Nn #5
      {
        \__tblr_data_item:nen { column }
          { \int_eval:n {#2 + \lTblrCellColSpanInt - 1} } { rightsep }
      }
    %\tl_log:e { cell[#1][#2] ~:~ \dim_use:N #3, \dim_use:N #4, \dim_use:N #5 }
  }

%%% --------------------------------------------------------
%%> \section{Tracing tabularray}
%%% --------------------------------------------------------

\NewDocumentCommand \SetTblrTracing { m }
  {
    \__tblr_keys_set:nn { tracing/set } {#1}
  }

\bool_new:N \g__tblr_tracing_text_bool
\bool_new:N \g__tblr_tracing_command_bool
\bool_new:N \g__tblr_tracing_option_bool
\bool_new:N \g__tblr_tracing_theme_bool
\bool_new:N \g__tblr_tracing_outer_bool
\bool_new:N \g__tblr_tracing_inner_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
\bool_new:N \g__tblr_tracing_intarray_bool
\bool_new:N \g__tblr_tracing_page_bool
\bool_new:N \g__tblr_tracing_step_bool

\__tblr_keys_define:nn { tracing/set }
  {
    +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,
    +option .code:n = \bool_gset_true:N \g__tblr_tracing_option_bool,
    -option .code:n = \bool_gset_false:N \g__tblr_tracing_option_bool,
    +theme .code:n = \bool_gset_true:N \g__tblr_tracing_theme_bool,
    -theme .code:n = \bool_gset_false:N \g__tblr_tracing_theme_bool,
    +outer .code:n = \bool_gset_true:N \g__tblr_tracing_outer_bool,
    -outer .code:n = \bool_gset_false:N \g__tblr_tracing_outer_bool,
    +inner .code:n = \bool_gset_true:N \g__tblr_tracing_inner_bool,
    -inner .code:n = \bool_gset_false:N \g__tblr_tracing_inner_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,
    +intarray .code:n = \bool_gset_true:N \g__tblr_tracing_intarray_bool,
    -intarray .code:n = \bool_gset_false:N \g__tblr_tracing_intarray_bool,
    +page .code:n = \bool_gset_true:N \g__tblr_tracing_page_bool,
    -page .code:n = \bool_gset_false:N \g__tblr_tracing_page_bool,
    +step .code:n = \bool_gset_true:N \g__tblr_tracing_step_bool,
    -step .code:n = \bool_gset_false:N \g__tblr_tracing_step_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_option_bool
    \bool_gset_true:N \g__tblr_tracing_theme_bool
    \bool_gset_true:N \g__tblr_tracing_outer_bool
    \bool_gset_true:N \g__tblr_tracing_inner_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
    \bool_gset_true:N \g__tblr_tracing_intarray_bool
    \bool_gset_true:N \g__tblr_tracing_page_bool
    \bool_gset_true:N \g__tblr_tracing_step_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_option_bool
    \bool_gset_false:N \g__tblr_tracing_theme_bool
    \bool_gset_false:N \g__tblr_tracing_outer_bool
    \bool_gset_false:N \g__tblr_tracing_inner_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
    \bool_gset_false:N \g__tblr_tracing_intarray_bool
    \bool_gset_false:N \g__tblr_tracing_page_bool
    \bool_gset_false:N \g__tblr_tracing_step_bool
  }

\NewDocumentCommand \LogTblrTracing { m }
  {
    \__tblr_keys_set:nn { tracing/log } {#1}
  }

\__tblr_keys_define:nn { tracing/log }
  {
    step .code:n = \__tblr_log_tracing_step:n {#1},
    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_spec_log:n { text }
  }

\cs_new_protected:Npn \__tblr_log_tracing_command:
  {
    \__tblr_prop_log:n { command }
  }

\cs_new_protected:Npn \__tblr_log_tracing_option:
  {
    \__tblr_prop_log:n { note }
    \__tblr_prop_log:n { remark }
    \__tblr_prop_log:n { more }
  }

\cs_new_protected:Npn \__tblr_log_tracing_theme:
  {
    \__tblr_style_log:
  }

\cs_new_protected:Npn \__tblr_log_tracing_outer:
  {
    \__tblr_spec_log:n { outer }
  }

\cs_new_protected:Npn \__tblr_log_tracing_inner:
  {
    \__tblr_prop_log:n { inner }
  }

\cs_new_protected:Npn \__tblr_log_tracing_column:
  {
    \__tblr_data_log:n { column }
  }

\cs_new_protected:Npn \__tblr_log_tracing_row:
  {
    \__tblr_data_log:n { row }
  }

\cs_new_protected:Npn \__tblr_log_tracing_cell:
  {
    \__tblr_data_log:n { cell }
  }

\cs_new_protected:Npn \__tblr_log_tracing_vline:
  {
    \__tblr_spec_log:n { vline }
  }

\cs_new_protected:Npn \__tblr_log_tracing_hline:
  {
    \__tblr_spec_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__tblr_column_target_dim
    \prop_log:N \l__tblr_column_coefficient_prop
    \prop_log:N \l__tblr_column_natural_width_prop
    \prop_log:N \l__tblr_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_log_tracing_page:
  {
    \dim_log:N \pagegoal
    \dim_log:N \pagetotal
  }

\cs_new_protected:Npn \__tblr_log_tracing_step:n #1
  {
    \bool_if:NT \g__tblr_tracing_step_bool { \tl_log:e {Step :~ #1} }
  }

\cs_new_protected:Npn \__tblr_do_if_tracing:nn #1 #2
  {
    \bool_if:cT { g__tblr_tracing_ #1 _bool } {#2}
  }

%%% --------------------------------------------------------
%%> \section{Tabularray libraries}
%%% --------------------------------------------------------

%% \NewTblrLibrary and \UseTblrLibrary commands

\NewDocumentCommand \NewTblrLibrary { m m }
  {
    \cs_new_protected:cpn { __tblr_use_lib_ #1: } {#2}
  }

%% Note that \prg_do_nothing: is an existing command.
\NewDocumentCommand \UseTblrLibrary { m }
  {
    \clist_map_inline:nn {#1}
      {
        \cs_if_exist:cTF { __tblr_use_lib_ ##1: }
          {
            \use:c { __tblr_use_lib_ ##1: }
            \cs_gset_eq:cN { __tblr_use_lib_ ##1: } \prg_do_nothing:
          }
          {
            \RequirePackage { tblrlib##1 }
          }
      }
  }

\prg_set_conditional:Npnn \__tblr_lib_if_used:n #1 { p, T, F, TF }
  {
    \tl_if_eq:cNTF { __tblr_use_lib_ #1: } \prg_do_nothing:
      { \prg_return_true: } { \prg_return_false: }
  }

%% Library amsmath and environments +array, +matrix, +cases, ...

\NewTblrLibrary { amsmath }
  {
    \RequirePackage { amsmath }
    \NewTblrEnviron { +array }
    \SetTblrInner[+array]{colsep = 5pt}
    \NewDocumentEnvironment { +matrix } { O{} +b } {
      \begin{+array}{
        column{1} = {leftsep = 0pt}, column{Z} = {rightsep = 0pt},
        cells = {c}, ##1
      }
        ##2
      \end{+array}
    } { }
    \NewDocumentEnvironment { +bmatrix } { O{} +b } {
      \begin{+array}{
        column{1} = {leftsep = 0pt}, column{Z} = {rightsep = 0pt},
        cells = {c}, delimiter = {left = [, right = ]}, ##1
      }
        ##2
      \end{+array}
    } { }
    \NewDocumentEnvironment { +Bmatrix } { O{} +b } {
      \begin{+array} {
        column{1} = {leftsep = 0pt}, column{Z} = {rightsep = 0pt},
        cells = {c}, delimiter = {left = \lbrace, right = \rbrace}, ##1
      }
        ##2
      \end{+array}
    } { }
    \NewDocumentEnvironment { +pmatrix } { O{} +b } {
      \begin{+array} {
        column{1} = {leftsep = 0pt}, column{Z} = {rightsep = 0pt},
        cells = {c}, delimiter = {left = (, right = )}, ##1
      }
        ##2
      \end{+array}
    } { }
    \NewDocumentEnvironment { +vmatrix } { O{} +b } {
      \begin{+array} {
        column{1} = {leftsep = 0pt}, column{Z} = {rightsep = 0pt},
        cells = {c}, delimiter = {left = \lvert, right = \rvert}, ##1
      }
        ##2
      \end{+array}
    } { }
    \NewDocumentEnvironment { +Vmatrix } { O{} +b } {
      \begin{+array} {
        column{1} = {leftsep = 0pt}, column{Z} = {rightsep = 0pt},
        cells = {c}, delimiter = {left = \lVert, right = \rVert}, ##1
      }
        ##2
      \end{+array}
    } { }
    \NewDocumentEnvironment { +cases } { O{} +b } {
      \begin{+array} {
        column{1} = {leftsep = 0pt}, column{Z} = {rightsep = 0pt},
        colspec = {ll}, stretch = 1.2, delimiter = {left=\lbrace, right=.}, ##1
      }
        ##2
      \end{+array}
    } { }
  }

%% Library booktabs and commands \toprule, \midrule, \bottomrule

\NewTblrLibrary { booktabs }
  {
    % We only use dimensions \aboverulesep and \belowrulesep in booktabs package
    \RequirePackage { booktabs }
    \newcommand \tblr@booktabs@hline [1] [] { \hline [##1] }
    \newcommand \tblr@booktabs@oldhline [1] [] {
      \hline [##1]
      \hborder { abovespace = \aboverulesep, belowspace = \belowrulesep }
    }
    \newcommand \tblr@booktabs@cline [2] [] { \cline [##1] {##2} }
    \newcommand \tblr@booktabs@oldcline [2] [] {
      \cline [##1] {##2}
      \hborder { abovespace = \aboverulesep, belowspace = \belowrulesep }
    }
    \newcommand \tblr@booktabs@cline@more [2] [] { \SetHline [+] {##2} {##1} }
    \newcommand \tblr@booktabs@oldcline@more [2] [] {
      \SetHline [+] {##2} {##1}
      \hborder { abovespace = \aboverulesep, belowspace = \belowrulesep }
    }
    \NewTblrTableCommand \toprule [1] [] {
      \tblr@booktabs@hline [wd=\heavyrulewidth, ##1]
    }
    \NewTblrTableCommand \midrule [1] [] {
      \tblr@booktabs@hline [wd=\lightrulewidth, ##1]
    }
    \NewTblrTableCommand \bottomrule [1] [] {
      \tblr@booktabs@hline [wd=\heavyrulewidth, ##1]
    }
    \NewTblrTableCommand \cmidrule [2] [] {
      \tblr@booktabs@cline [wd=\cmidrulewidth, endpos, ##1] {##2}
    }
    \NewTblrTableCommand \cmidrulemore [2] [] {
      \tblr@booktabs@cline@more [wd=\cmidrulewidth, endpos, ##1] {##2}
    }
    \newcommand \tblr@booktabs@change@more [1] { \cmidrulemore }
    \NewTblrTableCommand \morecmidrules {
      \peek_meaning:NTF \cmidrule { \tblr@booktabs@change@more } { \relax }
    }
    \NewTblrEnviron { booktabs }
    \NewTblrEnviron { longtabs }
    \NewTblrEnviron { talltabs }
    \SetTblrInner [ booktabs ] { rowsep = 0pt }
    \SetTblrInner [ longtabs ] { rowsep = 0pt }
    \SetTblrInner [ talltabs ] { rowsep = 0pt }
    \SetTblrOuter [ longtabs ] { long }
    \SetTblrOuter [ talltabs ] { tall }
    \RequirePackage { etoolbox }
    \newcommand \tblr@booktabs@begin@hook
      {
        \let \tblr@booktabs@hline = \tblr@booktabs@oldhline
        \let \tblr@booktabs@cline = \tblr@booktabs@oldcline
        \let \tblr@booktabs@cline@more = \tblr@booktabs@oldcline@more
      }
    \AtBeginEnvironment { booktabs } { \tblr@booktabs@begin@hook }
    \AtBeginEnvironment { longtabs } { \tblr@booktabs@begin@hook }
    \AtBeginEnvironment { talltabs } { \tblr@booktabs@begin@hook }
    \NewTblrTableCommand \specialrule [3]
      { \hline [##1] \hborder { abovespace = ##2, belowspace = ##3 } }
    \NewTblrTableCommand \addrowspace [1] [\defaultaddspace]
      { \hborder { abovespace+ = (##1) / 2, belowspace+ = (##1) / 2 } }
    \NewTblrTableCommand \addlinespace [1] [\defaultaddspace]
      { \hborder { abovespace+ = (##1) / 2, belowspace+ = (##1) / 2 } }
  }

%% Library counter for resetting all counters

\tl_new:N \l__tblr_saved_trial_counters_tl
\tl_new:N \l__tblr_saved_cell_counters_tl

\cs_new_protected:Npn \__tblr_save_counters:n #1 { }
\cs_new_protected:Npn \__tblr_restore_counters:n #1 { }

%% We use code from tabularx package for resetting all LaTeX counters,
%% where internal macro \cl@@ckpt looks like the following:
%% \@elt{page} \@elt{equation} \@elt{enumi} \@elt{enumii} \@elt{enumiii} ...

\NewTblrLibrary { counter }
  {
    \cs_set_protected:Npn \__tblr_save_counters:n ##1
      {
        \def \@elt ####1 { \global\value{####1} = \the\value{####1} \relax }
        \tl_set:ce { l__tblr_saved_ ##1 _counters_tl } { \cl@@ckpt }
        \let \@elt = \relax
      }
    \cs_set_protected:Npn \__tblr_restore_counters:n ##1
      {
        \tl_use:c { l__tblr_saved_ ##1 _counters_tl }
      }
  }

%% Library diagbox and command \diagbox

\NewTblrLibrary { diagbox }
  {
    \RequirePackage{ diagbox }
    \cs_set_eq:NN \__tblr_lib_saved_diagbox:w \diagbox
    \NewTblrContentCommand \diagbox [3] []
      {
        \__tblr_lib_diagbox_fix:n
          {
            \__tblr_lib_saved_diagbox:w
              [ leftsep=\leftsep, rightsep=\rightsep, ##1 ]
              { \__tblr_lib_diagbox_math_or_text:n {##2} }
              { \__tblr_lib_diagbox_math_or_text:n {##3} }
          }
      }
    \NewTblrContentCommand \diagboxthree [4] []
      {
        \__tblr_lib_diagbox_fix:n
          {
            \__tblr_lib_saved_diagbox:w
              [ leftsep=\leftsep, rightsep=\rightsep, ##1 ]
              { \__tblr_lib_diagbox_math_or_text:n {##2} }
              { \__tblr_lib_diagbox_math_or_text:n {##3} }
              { \__tblr_lib_diagbox_math_or_text:n {##4} }
          }
      }
  }

\cs_new_protected:Npn \__tblr_lib_diagbox_math_or_text:n #1
  {
    \bool_if:NTF \l__tblr_cell_math_mode_bool {$#1$} {#1}
  }

\box_new:N \l__tblr_diag_box

\cs_new_protected:Npn \__tblr_lib_diagbox_fix:n #1
  {
    \hbox_set:Nn \l__tblr_diag_box {#1}
    \box_set_ht:Nn \l__tblr_diag_box { \box_ht:N \l__tblr_diag_box - \abovesep }
    \box_set_dp:Nn \l__tblr_diag_box { \box_dp:N \l__tblr_diag_box - \belowsep }
    \box_use:N \l__tblr_diag_box
  }

%% Library functional with evaluate and process options

\cs_set_eq:NN \__tblr_functional_calculation: \prg_do_nothing:

\NewTblrLibrary { functional }
  {
    \RequirePackage { functional }
    %% Add outer specification "evaluate"
    \__tblr_keys_define:nn { table/outer }
      { evaluate .code:n = \__tblr_outer_gput_spec:nn { evaluate } {##1} }
    \tl_new:N \l__tblr_evaluate_tl
    \cs_set_protected:Npn \__tblr_hook_split_before:
      {
        \tl_set:Ne \l__tblr_evaluate_tl
          { \__tblr_spec_item:nn { outer } { evaluate } }
        \tl_if_empty:NF \l__tblr_evaluate_tl
          {
            \tl_if_eq:NnTF \l__tblr_evaluate_tl { all }
              {
                \tlSet \l__tblr_body_tl { \evalWhole {\expValue \l__tblr_body_tl} }
              }
              {
                \exp_last_unbraced:NNV
                \__tblr_evaluate_table_body:NN \l__tblr_body_tl \l__tblr_evaluate_tl
              }
          }
      }
    %% Evaluate every occurrence of the specified function
    %% Note that functional package runs every return processor inside a group
    %% #1: tl with table content; #2: function to be evaluated
    \tl_new:N \g__tblr_functional_result_tl
    \cs_new_protected:Npn \__tblr_evaluate_table_body:NN ##1 ##2
      {
        \tl_gclear:N \g__tblr_functional_result_tl
        \cs_set_protected:Npn \__tblr_evaluate_table_body_aux:w ####1 ##2
          {
            \tl_gput_right:Nn \g__tblr_functional_result_tl {####1}
            \peek_meaning:NTF \q_stop { \use_none:n } {##2}
          }
        \fun_run_return_processor:nn
          {
            \exp_last_unbraced:NV \__tblr_evaluate_table_body_aux:w \gResultTl
          }
          {
            \exp_last_unbraced:NV
              \__tblr_evaluate_table_body_aux:w ##1 ##2 \q_stop
          }
        \tl_set_eq:NN ##1 \g__tblr_functional_result_tl
      }
    %% Add inner specification "process"
    \__tblr_keys_define:nn { table/inner }
      { process .code:n = \__tblr_keys_gput:nn { process } {##1} }
    \cs_set:Npn \__tblr_functional_calculation:
      {
        \LogTblrTracing { step = do ~ functional ~ calculation }
        \__tblr_prop_item:nn { inner } { process }
      }
    \prgNewFunction \cellGetText { m m }
      {
        \expWhole { \__tblr_spec_item:nn { text } { [##1][##2] } }
      }
    \prgNewFunction \cellSetText { m m m }
      {
        \__tblr_spec_gput:nnn { text } { [##1][##2] } {##3}
      }
    \prgNewFunction \cellSetStyle { m m m }
      {
        \tblr_set_cell:nnnn {##1} {##2} {} {##3}
      }
    \prgNewFunction \rowSetStyle { m m }
      {
        \tblr_set_row:nnn {##1} {} {##2}
      }
    \prgNewFunction \columnSetStyle { m m }
      {
        \tblr_set_column:nnn {##1} {} {##2}
      }
    %% Evaluate every function in inner specification
    %% But we need to protect the value of "process" key
    \clist_new:N \l__tblr_fun_keyvalue_clist
    \cs_new_protected:Npn \__tblr_fun_i:n ##1
      {
        \clist_put_right:Nn \l__tblr_fun_keyvalue_clist { ##1 }
      }
    \cs_new_protected:Npn \__tblr_fun_ii:nn ##1 ##2
      {
        \tl_if_eq:nnTF { ##1 } { process }
          {
            \clist_put_right:Nn \l__tblr_fun_keyvalue_clist
              { ##1 = {\evalNone{##2}} }
          }
          {
            \clist_put_right:Nn \l__tblr_fun_keyvalue_clist
              { ##1 = {##2} }
          }
      }
    \cs_set_protected:Npn \__tblr_hook_parse_inner_spec_before:
      {
        \clist_clear:N \l__tblr_fun_keyvalue_clist
        \keyval_parse:NNV \__tblr_fun_i:n \__tblr_fun_ii:nn \l__tblr_inner_spec_tl
        \tlSet \l__tblr_inner_spec_tl
          { \evalWhole { \expValue \l__tblr_fun_keyvalue_clist } }
      }
  }

%% Library hook provides some public hooks

\NewTblrLibrary { hook }
  {
    %% We need varwidth to keep \lTblrMeasuringBool correct
    \UseTblrLibrary { varwidth }
    \tl_set:Nn \l__tblr_inner_spec_measure_tl { vstore }
    \cs_set_eq:NN \__tblr_hook_use:n \__tblr_hook_use_true:n
    %% Be careful lthooks will not remove any spaces in hook paths
    \__tblr_hook_new_pair:nn { trial/before } { trial/after }
    \__tblr_hook_new_pair:nn { table/before } { table/after }
    \__tblr_hook_new_pair:nn {   row/before } {   row/after }
    \__tblr_hook_new_pair:nn {  cell/before } {  cell/after }
    \__tblr_hook_new_pair:nn { private/output/before } { private/output/after }
  }

%% Library html provides more public variables
%% These variables can be used by tagpdf, tex4ht and lwarp packages

\bool_new:N \l__tblr_html_variables_bool

\NewTblrLibrary { html }
  {
    \bool_set_true:N \l__tblr_html_variables_bool
  }

%% Library nameref and its caption-ref template

\NewTblrLibrary { nameref }
  {
    \RequirePackage { nameref }
    \clist_if_in:NnF \lTblrRefMoreClist { nameref }
      {
        \clist_put_right:Nn \lTblrRefMoreClist { nameref }
        \DeclareTblrTemplate { caption-ref }{ nameref }
          {
            \tl_if_eq:NnTF \lTblrEntryTl { none }
              { \exp_args:NV \GetTitleString \lTblrCaptionTl }
              {
                \tl_if_empty:NTF \lTblrEntryTl
                  { \exp_args:NV \GetTitleString \lTblrCaptionTl }
                  { \exp_args:NV \GetTitleString \lTblrEntryTl }
              }
            \tl_set_eq:NN \@currentlabelname \GetTitleStringResult
          }
      }
  }

%% Library siunitx and S columns

\NewTblrLibrary { siunitx }
  {
    \RequirePackage { siunitx }
    \NewTblrColumnType { S } [1] [] { Q[si = {##1}, c] }
    \NewTblrColumnType { s } [1] [] { Q[si = {##1}, c, cmd = \TblrUnit] }
    \__tblr_data_new_key:nnn { cell } { si } { str }
    \__tblr_keys_define:nn { column/inner }
      {
        si .code:n = \__tblr_siunitx_setcolumn:n {##1}
      }
    \cs_new_protected:Npn \__tblr_siunitx_setcolumn:n ##1
      {
        \__tblr_column_gput_cell:nn { si } {##1}
        \__tblr_column_gput_cell:nn { cmd } { \TblrNum }
      }
    \NewDocumentCommand \TblrNum { m }
      {
        \__tblr_siunitx_process:Nn \tablenum {##1}
      }
    \NewDocumentCommand \TblrUnit { m }
      {
        \__tblr_siunitx_process:Nn \si {##1}
      }
    \cs_new_protected:Npn \__tblr_siunitx_process:Nn ##1 ##2
      {
        \tl_if_head_is_group:nTF {##2}
          { ##2 }
          {
            \group_begin:
            \tl_set:Ne \l_tmpa_tl
              {
                \__tblr_data_item:neen { cell }
                  { \int_use:N \c@rownum } { \int_use:N \c@colnum } { si }
              }
            \exp_args:NV \sisetup \l_tmpa_tl
            ##1 {##2}
            \group_end:
          }
      }
    \__tblr_keys_define:nn { cell/inner }   { guard .meta:n = { cmd = } }
    \__tblr_keys_define:nn { row/inner }    { guard .meta:n = { cmd = } }
    \__tblr_keys_define:nn { column/inner } { guard .meta:n = { cmd = } }
  }

%% Library tikz to add table nodes, cell nodes and corner nodes with tikz

\tl_new:N \g__tblr_tikz_below_code_tl
\tl_new:N \g__tblr_tikz_above_code_tl
\dim_new:N \l__tblr_cell_inner_wd_dim
\dim_new:N \l__tblr_cell_inner_dp_dim
\dim_new:N \l__tblr_cell_hanchor_dim
\dim_new:N \l__tblr_last_cell_table_ht_dim
\dim_new:N \l__tblr_last_cell_table_dp_dim
\dim_new:N \l__tblr_last_vline_wd_dim
\box_new:N \l__tblr_tikz_node_box

\NewTblrLibrary { tikz }
  {
    \UseTblrLibrary { hook }
    \RequirePackage { tikz }
    \usetikzlibrary { calc }
    \tikzset
      {
        tblr / cell ~ node / .style =
          {
            rectangle ,
            inner ~ sep = 0pt ,
            outer ~ sep = 0pt ,
            draw = none ,
            fill = none
          } ,
        tblr / cell / .style = { } ,
        tblr / table ~ node / .style =
          {
            rectangle ,
            inner ~ sep = 0pt ,
            outer ~ sep = 0pt ,
            draw = none ,
            fill = none
          } ,
        tblr / table / .style = { } ,
        tblr / overlay / .style = { }
      }
    \NewDocumentEnvironment { tblrtikzbelow } { +b }
      {
        \tl_gset:Nn \g__tblr_tikz_below_code_tl
          {
            \begin{tikzpicture}
              [
                remember ~ picture ,
                overlay ,
                name ~ prefix = tblr \g__tblr_name_str - ,
                tblr / overlay
              ]
              ##1
            \end{tikzpicture}
          }
      } { }
    \NewDocumentEnvironment { tblrtikzabove } { +b }
      {
        \tl_gset:Nn \g__tblr_tikz_above_code_tl
          {
            \begin{tikzpicture}
              [
                remember ~ picture ,
                overlay ,
                name ~ prefix = tblr \g__tblr_name_str - ,
                tblr / overlay
              ]
              ##1
            \end{tikzpicture}
          }
      } { }
    \cs_new_protected:Npn \__tblr_tikz_make_cell_node:
      {
        \hbox_set:Nn \l__tblr_tikz_node_box
          {
            \begin{tikzpicture}[remember ~ picture, overlay]
              \coordinate
                (
                  tblr \g__tblr_name_str -
                  \int_use:N \c@rownum - \int_use:N \c@colnum -
                  single
                )
                at ( 0pt , -\l__tblr_row_dp_dim );
              \node
                [
                  tblr / cell ~ node ,
                  tblr / cell ,
                  anchor = center ,
                  text ~ width = \l__tblr_cell_inner_wd_dim ,
                  text ~ height = \l__tblr_row_ht_dim ,
                  text ~ depth = \l__tblr_cell_inner_dp_dim
                ]
                (
                  tblr \g__tblr_name_str -
                  \int_use:N \c@rownum - \int_use:N \c@colnum
                )
                at
                (
                  \l__tblr_cell_hanchor_dim - 0.5 \l__tblr_cell_inner_wd_dim
                  ,
                  0.5 \l__tblr_row_ht_dim - 0.5 \l__tblr_cell_inner_dp_dim
                ) { } ;
            \end{tikzpicture}
          }
      }
    \cs_new_protected:Npn \__tblr_tikz_make_table_node:
      {
        \hbox_set:Nn \l__tblr_tikz_node_box
          {
            \begin{tikzpicture}[remember ~ picture, overlay]
              \node
                [
                  tblr / table ~ node ,
                  tblr / table ,
                  anchor = center ,
                  text ~ width = \lTblrTableWidthDim ,
                  text ~ height = \l__tblr_last_cell_table_ht_dim ,
                  text ~ depth = \l__tblr_last_cell_table_dp_dim
                ]
                (
                  tblr \g__tblr_name_str - table
                )
                at
                (
                  \l__tblr_last_vline_wd_dim - 0.5 \lTblrTableWidthDim
                  ,
                  0.5 \l__tblr_last_cell_table_ht_dim
                  - 0.5 \l__tblr_last_cell_table_dp_dim
                ) { } ;
            \end{tikzpicture}
          }
      }
    \cs_new_protected:Npn \__tblr_tikz_enable_cell_node:
      {
        \__tblr_get_cell_background_width:NNN
            \c@rownum \c@colnum \l__tblr_cell_inner_wd_dim
        \__tblr_get_cell_background_depth:NNN
            \c@rownum \c@colnum\l__tblr_cell_inner_dp_dim
        \dim_zero:N \l__tblr_cell_hanchor_dim
        \tl_set:Ne \l__tblr_c_tl
          {
            \__tblr_data_item:neen { cell }
              { \int_use:N \c@rownum } { \int_use:N \c@colnum } { colspan }
          }
        \int_compare:nNnT { \l__tblr_c_tl } > { 1 }
          {
            \int_step_inline:nn { \l__tblr_c_tl - 1 }
              {
                \dim_add:Nn \l__tblr_cell_hanchor_dim
                  {
                    \__tblr_spec_item:ne { vline }
                      { [ \int_eval:n { \c@colnum + ####1 } ] / @vline-width }
                    +
                    \__tblr_data_item:nen { column }
                      { \int_eval:n { \c@colnum + ####1 } } { @col-width }
                    +
                    \__tblr_data_item:nen { column }
                      { \int_eval:n { \c@colnum + ####1 } } { leftsep }
                    +
                    \__tblr_data_item:nen { column }
                      { \int_eval:n { \c@colnum + ####1 } } { rightsep }
                  }
              }
          }
        \__tblr_tikz_make_cell_node:
        \box_use:N \l__tblr_tikz_node_box
      }
    \cs_new_protected:Npn \__tblr_tikz_enable_table_node:
      {
        \int_compare:nNnT { \c@rownum } = { \c@rowcount }
          {
            \int_compare:nNnT { \c@colnum } = { \c@colcount }
              {
                \__tblr_get_table_width:
                \__tblr_get_table_height:
                \__tblr_get_table_depth:
                \dim_set:Nn \l__tblr_last_vline_wd_dim
                  {
                    \__tblr_spec_item:ne { vline }
                      { [ \int_eval:n { \c@colcount + 1 } ] / @vline-width }
                  }
                \__tblr_tikz_make_table_node:
                \box_use:N \l__tblr_tikz_node_box
              }
          }
      }
    \tl_new:N \l__tblr_tikz_corner_node_code_tl
    \cs_new_protected:Npn \__tblr_tikz_make_corner_node:
      {
        \tl_set:Nn \l__tblr_tikz_corner_node_code_tl
          { \coordinate (h1) at ($(table.north~west)!0.5!(1-1.north~west)$); }
        \int_step_inline:nnn { 2 } { \c@rowcount }
          {
            \tl_put_right:Ne \l__tblr_tikz_corner_node_code_tl
              {
                \exp_not:N \coordinate (h####1) at
                  (
                    h1 |-
                    {
                      $(\int_eval:n{####1-1}-1-single)!0.5!(####1-1.north~west)$
                    }
                  );
              }
          }
        \tl_put_right:Ne \l__tblr_tikz_corner_node_code_tl
          {
            \exp_not:N \coordinate (h\int_eval:n{\c@rowcount+1}) at
              (h1|-{$(\int_use:N\c@rowcount-1-single)!0.5!(table.south~east)$});
          }
        \tl_put_right:Nn \l__tblr_tikz_corner_node_code_tl
          { \coordinate (v1) at (h1); }
        \int_step_inline:nnn { 2 } { \c@colcount }
          {
            \tl_put_right:Ne \l__tblr_tikz_corner_node_code_tl
              {
                \exp_not:N \coordinate (v####1) at
                  (
                    v1 -|
                    {
                      $(1-\int_eval:n{####1-1}-single)!0.5!(1-####1.north~west)$
                    }
                  );
              }
          }
        \tl_put_right:Ne \l__tblr_tikz_corner_node_code_tl
          {
            \exp_not:N \coordinate (v\int_eval:n{\c@colcount+1}) at
              (v1-|{$(1-\int_use:N\c@colcount-single)!0.5!(table.south~east)$});
          }
      }
    \tl_const:Nn \c__tblr_portrait_long_tl { long }
    \cs_new_protected:Npn \__tblr_tikz_enable_corner_node:
      {
        \bool_lazy_or:nnT
          { ! \tl_if_eq_p:NN \lTblrPortraitTypeTl \c__tblr_portrait_long_tl }
          {
            \bool_lazy_and_p:nn
              { \int_compare_p:nNn { \lTblrRowFirstInt } = {1} }
              { \int_compare_p:nNn { \lTblrRowLastInt } = { \c@rowcount } }
          }
          {
            \__tblr_tikz_make_corner_node:
            \begin{tikzpicture}
              [
                remember ~ picture, overlay,
                name ~ prefix = tblr \g__tblr_name_str -
              ]
              \tl_use:N \l__tblr_tikz_corner_node_code_tl
            \end{tikzpicture}
          }
      }
    \AddToTblrHook { cell/after } [ tblrlibtikz ]
      {
        \bool_lazy_and:nnF
          { \tl_if_empty_p:N \g__tblr_tikz_below_code_tl }
          { \tl_if_empty_p:N \g__tblr_tikz_above_code_tl }
          {
            \__tblr_tikz_enable_cell_node:
            \__tblr_tikz_enable_table_node:
          }
      }
    \AddToTblrHook { private/output/before } [ tblrlibtikz ]
      {
        \bool_lazy_and:nnF
          { \tl_if_empty_p:N \g__tblr_tikz_below_code_tl }
          { \tl_if_empty_p:N \g__tblr_tikz_above_code_tl }
          {
            \__tblr_tikz_enable_corner_node:
            \noindent
            \tl_use:N \g__tblr_tikz_below_code_tl
          }
      }
    \AddToTblrHook { private/output/after } [ tblrlibtikz ]
      {
        \tl_use:N \g__tblr_tikz_above_code_tl
      }
    \AddToTblrHook { table/after } [ tblrlibtikz ]
      {
        %% We clear them here since they are used several times in longtblr
        \tl_gclear:N \g__tblr_tikz_below_code_tl
        \tl_gclear:N \g__tblr_tikz_above_code_tl
      }
  }

\cs_new_protected:Npn \__tblr_get_table_height:
  {
    \dim_zero:N \l__tblr_last_cell_table_ht_dim
    \int_step_inline:nn { \c@rowcount - 1 }
      {
        \dim_add:Nn \l__tblr_last_cell_table_ht_dim
          { \__tblr_valign_get_hline_total:n {##1} }
        \dim_add:Nn \l__tblr_last_cell_table_ht_dim
          { \__tblr_valign_get_row_total:n {##1} }
      }
    \dim_add:Nn \l__tblr_last_cell_table_ht_dim
      { \__tblr_valign_get_hline_total:n { \int_use:N \c@rowcount } }
    \dim_add:Nn \l__tblr_last_cell_table_ht_dim
      { \__tblr_valign_get_row_height:n { \int_use:N \c@rowcount } }
  }

\cs_new_protected:Npn \__tblr_get_table_depth:
  {
    \dim_set:Nn \l__tblr_last_cell_table_dp_dim
      { \__tblr_valign_get_row_depth:n { \int_use:N \c@rowcount } }
    \dim_add:Nn \l__tblr_last_cell_table_dp_dim
      { \__tblr_valign_get_hline_total:n { \int_eval:n { \c@rowcount + 1 } } }
  }

%% Library varwidth and measure option

\NewTblrLibrary { varwidth }
  {
    \RequirePackage { varwidth }
    \tl_set:Nn \l__tblr_inner_spec_measure_tl { vbox }
    \__tblr_keys_define:nn { table/inner }
      { measure .tl_set:N = \l__tblr_inner_spec_measure_tl }
  }

%% Library zref and its caption-ref template

\NewTblrLibrary { zref }
  {
    \RequirePackage { zref-user }
    \clist_if_in:NnF \lTblrRefMoreClist { zref }
      {
        \clist_put_right:Nn \lTblrRefMoreClist { zref }
        \DeclareTblrTemplate { caption-ref }{ zref }
          {
            \exp_args:NV \zlabel \lTblrLabelTl
          }
      }
  }