From 70486f94e9067354cad7d753c629ab8b30446f5d Mon Sep 17 00:00:00 2001
From: Karl Berry <karl@freefriends.org>
Date: Mon, 16 Dec 2019 21:42:56 +0000
Subject: sdaps (16dec19)

git-svn-id: svn://tug.org/texlive/trunk@53147 c570f23f-e606-0410-a88d-b1316a301751
---
 Master/texmf-dist/tex/latex/sdaps/sdapsarray.sty | 1015 ++++++++++++++++++++++
 1 file changed, 1015 insertions(+)
 create mode 100644 Master/texmf-dist/tex/latex/sdaps/sdapsarray.sty

(limited to 'Master/texmf-dist/tex/latex/sdaps/sdapsarray.sty')

diff --git a/Master/texmf-dist/tex/latex/sdaps/sdapsarray.sty b/Master/texmf-dist/tex/latex/sdaps/sdapsarray.sty
new file mode 100644
index 00000000000..c57c389b347
--- /dev/null
+++ b/Master/texmf-dist/tex/latex/sdaps/sdapsarray.sty
@@ -0,0 +1,1015 @@
+%%
+%% This is file `sdapsarray.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% sdapsarray.dtx  (with options: `package')
+%% 
+%% This is a generated file.
+%% 
+%% Copyright (C) 2015 by Benjamin Berg <benjamin@sipsolutions.net>
+%% 
+%% This work may be distributed and/or modified under the
+%% conditions of the LaTeX Project Public License, either version 1.3
+%% of this license or (at your option) any later version.
+%% The latest version of this license is in
+%%   http://www.latex-project.org/lppl.txt
+%% and version 1.3 or later is part of all distributions of LaTeX
+%% version 2005/12/01 or later.
+%% 
+\NeedsTeXFormat{LaTeX2e}[1999/12/01]
+\ProvidesPackage{sdapsarray}
+    [2015/04/10 v0.1 Initial version of SDAPS array package]
+\RequirePackage{expl3}[2011/08/23]
+\ExplSyntaxOn
+\ExplSyntaxOff
+
+\RequirePackage{xparse}
+\RequirePackage{sdapsbase}
+
+\ExplSyntaxOn
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\cs_if_exist:NF \ior_str_get:NN { \cs_set_eq:Nc \ior_str_get:NN { ior_get_str:NN } }
+
+\bool_new:N \g__sdaps_array_info_open
+\bool_gset_false:N \g__sdaps_array_info_open
+\iow_new:N \g__sdaps_array_info_iow
+\ior_new:N \g__sdaps_array_info_ior
+
+\cs_generate_variant:Nn \int_set:Nn { Nf }
+\cs_generate_variant:Nn \coffin_join:NnnNnnnn { NnVNnVnn }
+
+\cs_new_protected_nopar:Nn \_sdaps_array_open_tmpfiles:
+{
+  % Guard against being executed multiple times
+  \bool_if:NF \g__sdaps_array_info_open {
+    \bool_gset_true:N \g__sdaps_array_info_open
+
+    % Also ensures toc file exists (i.e. is readable)
+    \iow_open:Nn \g__sdaps_array_info_iow { \c_sys_jobname_str .sdapsarraytoc }
+    \file_if_exist:nTF { \c_sys_jobname_str .sdapsarraytic } {
+      % Copy into toc file, then open that.
+      \ior_open:Nn \g__sdaps_array_info_ior { \c_sys_jobname_str .sdapsarraytic }
+
+      \ior_str_map_inline:Nn \g__sdaps_array_info_ior { \iow_now:Nn \g__sdaps_array_info_iow { ##1 } }
+      \ior_close:N \g__sdaps_array_info_ior
+
+      \ior_open:Nn \g__sdaps_array_info_ior { \c_sys_jobname_str .sdapsarraytoc }
+    } {
+    }
+    \iow_close:N \g__sdaps_array_info_iow
+
+    \iow_open:Nn \g__sdaps_array_info_iow { \c_sys_jobname_str .sdapsarraytic }
+  }
+}
+
+\tl_new:N \g__sdaps_array_last_row_tl
+\tl_gset_eq:NN \g__sdaps_array_last_row_tl \c_empty_tl
+
+\cs_new_protected_nopar:Nn \_sdaps_array_check_insert_header:N
+{
+  \bool_gset_eq:NN #1 \c_false_bool
+  \ior_if_eof:NF \g__sdaps_array_info_ior {
+    \ior_str_get:NN \g__sdaps_array_info_ior \l_tmpa_tl
+
+    \tl_if_eq:NNF \g__sdaps_array_last_row_tl \c_empty_tl {
+      \tl_if_eq:NNF \g__sdaps_array_last_row_tl \l_tmpa_tl {
+        \bool_gset_eq:NN #1 \c_true_bool
+      }
+    }
+    \tl_gset_eq:NN \g__sdaps_array_last_row_tl \l_tmpa_tl
+  }
+}
+
+
+\prop_new:N \g__sdaps_array_layouter_prop
+
+\int_new:N \g_sdaps_array_row_penalty_tl
+\int_gset:Nn \g_sdaps_array_row_penalty_tl { 10 }
+
+
+\tl_new:N \g_sdaps_array_shared_data_tl
+\tl_new:N \g_sdaps_array_stored_data_tl
+\tl_new:N \g_sdaps_array_local_data_tl
+\tl_new:N \g_sdaps_array_local_data_new_tl
+\prop_new:N \g__sdaps_array_stored_data_prop
+\prop_new:N \g__sdaps_array_shared_data_prop
+\prop_new:N \g__sdaps_array_local_data_prop
+
+\cs_generate_variant:Nn \prop_item:Nn { NV }
+
+\cs_new_protected_nopar:Nn \_sdaps_array_load_data:
+{
+  \tl_gset:Nx \g_sdaps_array_stored_data_tl { \prop_item:NV \g__sdaps_array_stored_data_prop \l__sdaps_array_global_name_tl }
+  \tl_gset:Nx \g_sdaps_array_shared_data_tl { \prop_item:NV \g__sdaps_array_shared_data_prop \l__sdaps_array_global_name_tl }
+  \tl_gset:Nx \g_sdaps_array_local_data_tl { \prop_item:NV \g__sdaps_array_local_data_prop \l__sdaps_array_local_name_tl }
+}
+
+\cs_new_protected_nopar:Nn \_sdaps_array_store_data:
+{
+  % Do not overwrite the "stored" data that we have right now.
+  \prop_gput:NVV \g__sdaps_array_shared_data_prop \l__sdaps_array_global_name_tl \g_sdaps_array_shared_data_tl
+
+  \immediate\write\@auxout{\exp_not:n{\sdapsarrayloadstoreddata}{\l__sdaps_array_global_name_tl}{\g_sdaps_array_shared_data_tl}}
+  \tl_if_empty:NF \g_sdaps_array_local_data_new_tl {
+    \immediate\write\@auxout{\exp_not:n{\sdapsarrayloadlocaldata}{\l__sdaps_array_local_name_tl}{\g_sdaps_array_local_data_new_tl}}
+  }
+}
+
+\cs_new_protected_nopar:Nn \sdaps_array_load_stored_data:nn {
+  \prop_gput:Nnn \g__sdaps_array_stored_data_prop { #1 } { #2 }
+}
+\cs_new_eq:NN \sdapsarrayloadstoreddata \sdaps_array_load_stored_data:nn
+
+\cs_new_protected_nopar:Nn \sdaps_array_load_local_data:nn {
+  \prop_gput:Nnn \g__sdaps_array_local_data_prop { #1 } { #2 }
+}
+\cs_new_eq:NN \sdapsarrayloadlocaldata \sdaps_array_load_local_data:nn
+
+
+\int_new:N \g__sdaps_array_current_id_int
+\tl_new:N \l__sdaps_array_global_name_tl
+\tl_new:N \l__sdaps_array_local_name_tl
+
+\cs_generate_variant:Nn \keys_set:nn { nf }
+\cs_new_protected_nopar:Nn \sdaps_array_begin:nn
+{
+ \tl_set:Nx \l__sdaps_array_local_name_tl { sdapsarray \int_use:N\g__sdaps_array_current_id_int }
+  \int_gincr:N \g__sdaps_array_current_id_int
+  \tl_if_empty:nTF { #2 } {
+    \tl_set:NV \l__sdaps_array_global_name_tl \l__sdaps_array_local_name_tl
+  } {
+    \tl_set:Nx \l__sdaps_array_global_name_tl { #2 }
+  }
+
+  \_sdaps_array_load_data:
+
+  \keys_set:nf { sdaps / array } { \prop_item:Nn \g__sdaps_array_layouter_prop { #1 } }
+
+  \_sdaps_array_open_tmpfiles:
+
+  \tl_gset_eq:NN \g__sdaps_array_last_row_tl \c_empty_tl
+  \bool_gset_true:N \g_sdaps_array_first_row_bool
+
+  \l__sdaps_array_begin_tl
+}
+\cs_generate_variant:Nn \sdaps_array_begin:nn { Vn }
+\cs_generate_variant:Nn \sdaps_array_begin:nn { VV }
+\cs_generate_variant:Nn \sdaps_array_begin:nn { nV }
+
+\cs_new_protected_nopar:Nn \sdaps_array_begin:n
+{
+  \sdaps_array_begin:nn { #1 } { }
+}
+
+\cs_new_protected_nopar:Nn \sdaps_array_row_start:
+{
+  \l__sdaps_array_row_start_tl
+}
+
+\cs_new_protected_nopar:Nn \_sdaps_array_assign_use: {
+    \box_use:N \l_tmpa_box
+  \egroup
+}
+
+\cs_new_protected_nopar:Npn \sdaps_array_assign_colhead:Nw #1
+{
+  \l__sdaps_array_colhead_tl #1
+}
+
+\cs_new_protected_nopar:Npn \sdaps_array_colhead:w
+{
+  \bgroup
+    \l__sdaps_array_colhead_tl \l_tmpa_box \bgroup
+    \group_insert_after:N\_sdaps_array_assign_use:
+    % swallow group opening token
+    \tex_let:D\next=
+}
+
+\cs_new_protected:Npn \sdaps_array_assign_rowhead:Nw #1
+{
+  \l__sdaps_array_rowhead_tl #1
+}
+
+\cs_new_protected:Npn \sdaps_array_rowhead:w
+{
+  \bgroup
+    \l__sdaps_array_rowhead_tl \l_tmpa_box \bgroup
+    \group_insert_after:N\_sdaps_array_assign_use:
+    % swallow group opening token
+    \tex_let:D\next=
+}
+
+\cs_new_protected_nopar:Npn \sdaps_array_assign_cell:Nw #1
+{
+  \l__sdaps_array_cell_tl #1
+}
+
+\cs_new_protected_nopar:Npn \sdaps_array_cell:w
+{
+  \bgroup
+    \l__sdaps_array_cell_tl \l_tmpa_box \bgroup
+    \group_insert_after:N\_sdaps_array_assign_use:
+    % swallow group opening token
+    \tex_let:D\next=
+}
+
+\box_new:N \g__sdaps_array_header_box
+\dim_new:N \g__sdaps_array_header_dim
+
+\cs_new_protected_nopar:Nn \_sdaps_array_calc_interlineskip:nnN
+{
+  \dim_compare:nNnTF { #1 } > { -1000pt } {
+    \skip_set:Nn #3 { \baselineskip - #1 - #2 }
+    \dim_compare:nNnF { #3 } > { \lineskiplimit } {
+      \skip_set:Nn #3 { \lineskip }
+    }
+  } {
+    \skip_set:Nn #3 { 0pt }
+  }
+  \skip_set:Nn #3 { #3 + \l_sdaps_sdapsarray_rowsep_dim }
+}
+
+\cs_new_protected_nopar:Nn \sdaps_array_row:NN
+{
+  \if_mode_vertical:
+  \else:
+    \msg_error:nn { sdapsarray } { wrong_mode }
+  \fi
+
+  % Pagebreak detection
+  \_sdaps_array_check_insert_header:N \g_tmpa_bool
+  % XXX: \l_tmpa_dim is the height to the first baseline in the box. Note that
+  %      we use the real baseline in the case of the header row!
+  \l__sdaps_array_row_tl #1 #2 \l_tmpb_box \l_tmpa_dim
+
+  \bool_if:NT \g_sdaps_array_first_row_bool {
+    % Stow away the box for later use (before rewriting it to save the position)
+    \box_gset_eq:NN \g__sdaps_array_header_box \l_tmpb_box
+    \dim_gset:Nn \g__sdaps_array_header_dim { \box_ht:N \g__sdaps_array_header_box + \box_dp:N \g__sdaps_array_header_box }
+  }
+
+  \hbox_set:Nn \l_tmpb_box {
+    \pdfsavepos
+    \iow_shipout_x:Nn \g__sdaps_array_info_iow {
+      \thepage,
+      \the\pdflastxpos
+    }
+    \box_use:N \l_tmpb_box
+  }
+
+  \bool_if:NTF \g_sdaps_array_first_row_bool {
+    \bool_gset_false:N \g_sdaps_array_first_row_bool
+
+    \box_use:N \l_tmpb_box
+    % Do not ever allow a break after the header line.
+    \nobreak
+  } {
+    % The idea is simple. Before every line the header is re-inserted (either
+    % the real one or an empty box with the same dimensions). In the case that
+    % there is *no* page break we insert a corresponding negative skip so that
+    % the resulting skip (including interline skip) is exactly the normal
+    % interline skip between the rows.
+    % In the case that the skip *is* discarded we end up with the header box
+    % and the normal interline skip between the new row and the header. i.e.:
+    %   skip = 1 * ( interlineskip_for_row - interlineskip_for_header - header_height - header_depth ) + header_height + header_depth + interlineksip_for_header
+    % or
+    %   skip = 0 * ( interlineskip_for_row - interlineskip_for_header - header_height - header_depth ) + header_height + header_depth + interlineksip_for_header
+
+    % Calculate interlineksip_for_row and interlineskip_for_header
+    \_sdaps_array_calc_interlineskip:nnN { \prevdepth } { \l_tmpa_dim } \l_tmpa_skip
+    \_sdaps_array_calc_interlineskip:nnN { \box_dp:N \g__sdaps_array_header_box } { \l_tmpa_dim } \l_tmpb_skip
+    \nointerlineskip
+    \skip_vertical:n { \l_tmpa_skip - \l_tmpb_skip }
+    \kern -\g__sdaps_array_header_dim
+
+    % Inser the real or fake box
+    \bool_if:NTF \g_tmpa_bool {
+      \box_use:N \g__sdaps_array_header_box
+    } {
+     \hrule height \box_ht:N \g__sdaps_array_header_box depth \box_dp:N \g__sdaps_array_header_box width 0pt
+    }
+    \nobreak
+    % Insert the calculated interline skip (in the same way the TeX would do it.
+    \nointerlineskip
+    \skip_vertical:N \l_tmpb_skip
+
+    \box_use:N \l_tmpb_box
+
+    % And insert the sdapsarray sepecific inter row penalty.
+    \penalty\int_use:N\g_sdaps_array_row_penalty_tl
+  }
+}
+
+\cs_new_protected_nopar:Nn \sdaps_array_end:
+{
+  \l__sdaps_array_end_tl
+  \box_gclear:N \g__sdaps_array_header_box
+}
+
+
+\tl_new:N \l__sdaps_array_begin_tl
+\tl_new:N \l__sdaps_array_row_start_tl
+\tl_new:N \l__sdaps_array_colhead_tl
+\tl_new:N \l__sdaps_array_rowhead_tl
+\tl_new:N \l__sdaps_array_cell_tl
+\tl_new:N \l__sdaps_array_row_tl
+\tl_new:N \l__sdaps_array_end_tl
+
+\keys_define:nn { sdaps / array }
+{
+  begin        .tl_set:N   = \l__sdaps_array_begin_tl,
+  row_start    .tl_set:N   = \l__sdaps_array_row_start_tl,
+  colhead      .tl_set:N   = \l__sdaps_array_colhead_tl,
+  rowhead      .tl_set:N   = \l__sdaps_array_rowhead_tl,
+  cell         .tl_set:N   = \l__sdaps_array_cell_tl,
+  row          .tl_set:N   = \l__sdaps_array_row_tl,
+  end          .tl_set:N   = \l__sdaps_array_end_tl,
+}
+
+\seq_new:N \g_sdaps_array_overhangs_left_seq
+\seq_new:N \g_sdaps_array_overhangs_right_seq
+\seq_new:N \g_sdaps_array_shared_colwidths_seq
+\seq_new:N \g_sdaps_array_stored_colwidths_seq
+
+\cs_new_protected_nopar:Npn \_sdaps_array_rowhead_default:Nw #1
+{
+  \tl_if_empty:NTF \g_sdaps_array_local_data_tl {
+      \tl_if_empty:NTF \g_sdaps_array_local_data_new_tl {
+        \dim_set:Nn \l_tmpa_dim { 0.5 \hsize }
+      } {
+        \dim_set:Nn \l_tmpa_dim { \g_sdaps_array_local_data_new_tl }
+      }
+  } {
+    \dim_set:Nn \l_tmpa_dim { \g_sdaps_array_local_data_tl }
+  }
+  % \vbox_set_top:Nw is still missing as of 2017-08-11
+  \tex_setbox:D #1 \tex_vtop:D \bgroup
+    \sdaps_if_rtl:TF {
+      \raggedright
+    } {
+      \raggedleft
+    }
+    \hsize=\dim_use:N\l_tmpa_dim
+    \group_begin:\bgroup
+    \group_insert_after:N \group_end:
+    \group_insert_after:N \egroup
+    % swallow group opening token
+    \tex_let:D\next=
+}
+
+\cs_new_protected_nopar:Npn \_sdaps_array_cell_default:Nw #1
+{
+  \hbox_set:Nw #1 \bgroup
+    % swallow group opening token
+    \group_insert_after:N \hbox_set_end:
+    \tex_let:D\next=
+}
+
+\cs_new:Nn \_sdaps_array_cell_rotated_end: {
+    \hbox_set_end:
+    \dim_set:Nn \l_tmpa_dim { \box_ht:N \l_tmpa_box }
+    \dim_set:Nn \l_tmpb_dim { \box_dp:N \l_tmpa_box }
+
+    \dim_set:Nn \l_tmpa_dim { \l_sdaps_sdapsarray_angle_sine_tl \l_tmpa_dim }
+    \dim_set:Nn \l_tmpb_dim { \l_sdaps_sdapsarray_angle_sine_tl \l_tmpb_dim }
+
+    \box_rotate:Nn \l_tmpa_box { \l_sdaps_sdapsarray_angle_int }
+
+    % We want the baseline of the box to be centered, that only works if we
+    % leave the same space both ways.
+    % That is not ideal, but we cannot move the cell content accordingly.
+    \dim_set:Nn \l_tmpb_dim { \dim_max:nn { \l_tmpa_dim } { \l_tmpb_dim } }
+    \skip_horizontal:n { \l_tmpb_dim }
+    \rlap{
+      \skip_horizontal:n { -\l_tmpa_dim }
+      \box_use:N \l_tmpa_box
+    }
+    \skip_horizontal:n { \l_tmpb_dim }
+    % dummy skip that will be removed again by other code
+    \skip_horizontal:n { 0pt }
+
+    \dim_set:Nn \l_tmpa_dim { \l_tmpa_dim + \l_tmpb_dim }
+
+    \dim_set:Nn \l_tmpb_dim { \box_wd:N \l_tmpa_box }
+    \dim_set:Nn \l_tmpa_dim { \dim_max:nn { 0pt } { \l_tmpb_dim - \l_tmpa_dim } }
+
+    \seq_gpush:Nn \g_sdaps_array_overhangs_left_seq { 0pt }
+    \seq_gpush:Nx \g_sdaps_array_overhangs_right_seq { \dim_use:N \l_tmpa_dim }
+  \egroup
+  \hbox_set_end:
+}
+
+\cs_new_protected_nopar:Npn \_sdaps_array_cell_rotated:Nw #1
+{
+  \hbox_set:Nw #1 \bgroup
+    \hbox_set:Nw \l_tmpa_box
+    \bgroup
+    \group_insert_after:N \_sdaps_array_cell_rotated_end:
+    % swallow group opening token
+    \tex_let:D\next=
+}
+
+
+\cs_new_protected_nopar:Nn \_sdaps_array_begin_default:
+{
+  \tl_if_empty:NTF \g_sdaps_array_shared_data_tl {
+    \seq_clear:N \g_sdaps_array_shared_colwidths_seq
+  } {
+    \seq_gset_split:NnV \g_sdaps_array_shared_colwidths_seq { ~ } \g_sdaps_array_shared_data_tl
+  }
+  \tl_if_empty:NTF \g_sdaps_array_stored_data_tl {
+    \seq_clear:N \g_sdaps_array_stored_colwidths_seq
+  } {
+    \seq_gset_split:NnV \g_sdaps_array_stored_colwidths_seq { ~ } \g_sdaps_array_stored_data_tl
+  }
+}
+
+\cs_new_protected_nopar:Nn \_sdaps_array_end_default:
+{
+  \tl_gset:Nx \g_sdaps_array_shared_data_tl { \seq_use:Nn \g_sdaps_array_shared_colwidths_seq { ~ } }
+  \tl_gset:Nx \g_sdaps_array_stored_data_tl { \seq_use:Nn \g_sdaps_array_stored_colwidths_seq { ~ } }
+
+  % Clear the global sequences, to save memory
+  \seq_gclear:N \g_sdaps_array_overhangs_left_seq
+  \seq_gclear:N \g_sdaps_array_overhangs_right_seq
+  \seq_gclear:N \g_sdaps_array_shared_colwidths_seq
+  \seq_gclear:N \g_sdaps_array_stored_colwidths_seq
+
+  \_sdaps_array_store_data:
+}
+
+\cs_new_protected_nopar:Nn \_sdaps_array_row_start_default:
+{
+  \seq_gclear:N \g_sdaps_array_overhangs_left_seq
+  \seq_gclear:N \g_sdaps_array_overhangs_right_seq
+}
+
+\cs_new_protected_nopar:Nn \_sdaps_array_row:NNNN
+{
+  % #1: A vbox with baseline on the *first* item containing the row header
+  %     (\vtop in plain TeX).
+  % #2: Data cells packed into an hbox. Each of these needs to be set to the
+  %     correct width and inserted.
+  % #3: The box register to store the resulting hbox in. The depth of this box
+  %     needs to be correct to calculate the interline glue to the following
+  %     row.
+  % #4: A dim register to store the height of the box in for the purpose of
+  %     calculating the interline glue in front of the produced row.
+  %
+  % { \dim_use:N\@totalleftmargin } { \dim_use:N\linewidth }
+  % The macro should create an hbox which is exactly \linewidth wide and also
+  % contains internal indentation by \@totalleftmargin into the box register #3.
+  % The box will be used while in vertical mode and may% be inserted multiple
+  % times in the case of the header row.
+  % To simplify the iteration it is guaranteed that the data cell boxes are not
+  % completely empty. This means the code can simply unbox until it sees a box
+  % that is void.
+
+  \seq_gclear:N \g_tmpa_seq
+
+  % Insert the boxes into a local hbox to work with them
+  \hbox_set:Nn #2 {
+    \hbox_unpack:N #2
+
+    % Handle the overhang, note that we modify the \g_sdaps_array_overhangs_right_seq locally only!
+    \seq_pop:NNTF \g_sdaps_array_overhangs_right_seq \l_tmpa_tl {
+      \dim_set:Nn \l_tmpb_dim { \l_tmpa_tl }
+    } {
+      \dim_set:Nn \l_tmpb_dim { 0pt }
+    }
+    % Implicit "last" column that contains the overhang
+    \seq_gpop:NNTF \g_sdaps_array_shared_colwidths_seq \l_tmpa_tl {
+      \dim_set:Nn \l_tmpa_dim { \l_tmpa_tl }
+    } {
+      \dim_set:Nn \l_tmpa_dim { 0pt }
+    }
+    \dim_set:Nn \l_tmpa_dim { \dim_max:nn { \l_tmpa_dim } { \l_tmpb_dim } }
+
+    % MAX with stored values (NOTE: sequence only modified in local scope)
+    \seq_pop:NNTF \g_sdaps_array_stored_colwidths_seq \l_tmpa_tl {
+      \dim_set:Nn \l_tmpb_dim { \l_tmpa_tl }
+    } {
+      \dim_set:Nn \l_tmpb_dim { 0pt }
+    }
+    % Store value from this run, and then calculate max with previous run
+    \seq_gput_right:Nx \g_tmpa_seq { \dim_use:N \l_tmpa_dim }
+    \dim_set:Nn \l_tmpa_dim { \dim_max:nn { \l_tmpa_dim } { \l_tmpb_dim } }
+
+    % Insert the overhang space
+    \hbox_set:Nn \l_tmpa_box { \skip_horizontal:n { \l_tmpa_dim } }
+
+    % Now grab the first of the cells, and then loop over the rest
+    \box_set_to_last:N #2
+    \bool_do_while:nn { ! \box_if_empty_p:N #2 } {
+      % Strip any trailing glue (i.e. space) coming from the user (for the
+      % leading side we ensure that \tex_ignorespaces:D is called).
+      % Note that this may remove e.g. a trailing \hfill from the user, the
+      % user needs to work around that (in the same way as is required in e.g.
+      % tabular).
+      \hbox_set:Nn #2 { \hbox_unpack:N #2 \unskip }
+
+      % Pop the target width for the current box (i.e. we don't globally
+      % modify the clist here).
+      \seq_gpop:NNTF \g_sdaps_array_shared_colwidths_seq \l_tmpa_tl {
+        \dim_set:Nn \l_tmpa_dim { \l_tmpa_tl }
+      } {
+        \dim_set:Nn \l_tmpa_dim { 0pt }
+      }
+      % Calculate the maximum width of current and previous items
+      \dim_set:Nn \l_tmpa_dim { \dim_max:nn { \box_wd:N #2 } { \l_tmpa_dim } }
+
+      % MAX with stored values (NOTE: sequence only modified in local scope)
+      \seq_pop:NNTF \g_sdaps_array_stored_colwidths_seq \l_tmpa_tl {
+        \dim_set:Nn \l_tmpb_dim { \l_tmpa_tl }
+      } {
+        \dim_set:Nn \l_tmpb_dim { 0pt }
+      }
+      % Store value from this run, and then calculate max with previous run
+      \seq_gput_right:Nx \g_tmpa_seq { \dim_use:N \l_tmpa_dim }
+      \dim_set:Nn \l_tmpa_dim { \dim_max:nn { \l_tmpa_dim } { \l_tmpb_dim } }
+
+      % Set the box into a new box with the correct width which contains fil
+      % to center it.
+      \hbox_set_to_wd:Nnn \l_tmpb_box \l_tmpa_dim { \hfil \hbox_unpack:N #2 \hfil }
+
+      % This loops works backward, so attach the cell on the right side.
+      % We used to make sure that it is layed out in order, but that is now
+      % obsolete and doing it out of order is simpler in RTL mode.
+      \sdaps_if_rtl:TF {
+        % The boxes are shown on the page from LTR
+        \hbox_set:Nn \l_tmpa_box { \box_use:N \l_tmpa_box \skip_horizontal:n { \l_sdaps_sdapsarray_colsep_dim } \box_use:N \l_tmpb_box \skip_horizontal:n { \l_sdaps_sdapsarray_colsep_dim } }
+      } {
+        \hbox_set:Nn \l_tmpa_box { \skip_horizontal:n { \l_sdaps_sdapsarray_colsep_dim } \box_use:N \l_tmpb_box \skip_horizontal:n { \l_sdaps_sdapsarray_colsep_dim } \box_use:N \l_tmpa_box }
+      }
+
+      % Grab next cell
+      \box_set_to_last:N #2
+    }
+
+    % Get the coffin out of the nested scope by placing it into the box and
+    % placing that into it again ...
+    \box_use:N \l_tmpa_box
+  }
+  \hcoffin_set:Nn \l_tmpa_coffin { \box_use_drop:N #2 }
+
+  \seq_gconcat:NNN \g_sdaps_array_shared_colwidths_seq \g_tmpa_seq \g_sdaps_array_shared_colwidths_seq
+  \seq_gclear:N \g_tmpa_seq
+
+  % Calculate the space that is left for the header column
+  \dim_set:Nn \l_tmpa_dim { \linewidth - \coffin_wd:N \l_tmpa_coffin - 2\l_sdaps_sdapsarray_colsep_dim }
+  \tl_gset:Nx \g_sdaps_array_local_data_new_tl { \dim_use:N \l_tmpa_dim }
+
+  % TODO: The \hfil here is a hack to prevent a warning if the vbox is empty.
+  %       Unfortunately checking for an empty box does not work for some reason.
+  \dim_set:Nn \l_tmpb_dim { \box_ht:N #1 }
+  \sdaps_if_rtl:TF {
+    \hcoffin_set:Nn \l_tmpb_coffin { \hbox_to_wd:nn \l_tmpa_dim { \hfil \vbox:n { \vbox_unpack_clear:N #1 } } \skip_horizontal:n { \l_sdaps_sdapsarray_colsep_dim } }
+    \tl_set:Nn \l_tmpa_tl { l }
+    \tl_set:Nn \l_tmpb_tl { r }
+  } {
+    \hcoffin_set:Nn \l_tmpb_coffin { \skip_horizontal:n { \l_sdaps_sdapsarray_colsep_dim } \hbox_to_wd:nn \l_tmpa_dim { \hfil \vbox:n { \vbox_unpack_clear:N #1 } } }
+    \tl_set:Nn \l_tmpa_tl { r }
+    \tl_set:Nn \l_tmpb_tl { l }
+  }
+  \dim_set:Nn \l_tmpa_dim { \coffin_ht:N \l_tmpb_coffin }
+
+  % If the first/last baseline differ then center the vbox, otherwise align the
+  % baseline with the cells
+  \dim_compare:nNnTF { \l_tmpa_dim } = { \l_tmpb_dim } {
+    \coffin_join:NnVNnVnn \l_tmpb_coffin { H } \l_tmpa_tl \l_tmpa_coffin { H } \l_tmpb_tl { \l_sdaps_sdapsarray_colsep_dim } { 0pt }
+    \dim_set:Nn #4 { \coffin_ht:N \l_tmpb_coffin }
+  } {
+    \coffin_join:NnVNnVnn \l_tmpb_coffin { vc } \l_tmpa_tl \l_tmpa_coffin { vc } \l_tmpb_tl { \l_sdaps_sdapsarray_colsep_dim } { 0pt }
+    % XXX: Assume that the header is higher than the content cells
+    \dim_set:Nn #4 { \l_tmpb_dim }
+  }
+
+  \hbox_set:Nn #3 { \skip_horizontal:N \@totalleftmargin \coffin_typeset:Nnnnn \l_tmpb_coffin { H } { l } { 0pt } { 0pt } }
+}
+
+\prop_gput:Nnn \g__sdaps_array_layouter_prop { default } {
+  begin = { \_sdaps_array_begin_default: },
+  row_start = { \_sdaps_array_row_start_default: },
+  rowhead = { \_sdaps_array_rowhead_default:Nw },
+  colhead = { \_sdaps_array_cell_default:Nw },
+  cell = { \_sdaps_array_cell_default:Nw },
+  row = { \_sdaps_array_row:NNNN },
+  end = { \_sdaps_array_end_default: },
+}
+
+\prop_gput:Nnn \g__sdaps_array_layouter_prop { rotated } {
+  begin = { \_sdaps_array_begin_default: },
+  row_start = { \_sdaps_array_row_start_default: },
+  rowhead = { \_sdaps_array_rowhead_default:Nw },
+  colhead = { \_sdaps_array_cell_rotated:Nw },
+  cell = { \_sdaps_array_cell_default:Nw },
+  row = { \_sdaps_array_row:NNNN },
+  end = { \_sdaps_array_end_default: },
+}
+
+
+\bool_new:N \g_sdaps_array_first_row_bool
+\bool_new:N \l__sdaps_sdapsarray_in_top_group_bool
+\bool_new:N \l__sdaps_sdapsarray_have_content_bool
+\bool_new:N \l_sdaps_sdapsarray_flip_bool
+\tl_new:N \l_sdaps_sdapsarray_layouter_tl
+\tl_new:N \l_sdaps_sdapsarray_align_tl
+\bool_new:N \l_sdaps_sdapsarray_keepenv_bool
+\int_new:N \l_sdaps_sdapsarray_angle_int
+\tl_new:N \l_sdaps_sdapsarray_angle_sine_tl
+\dim_new:N \l_sdaps_sdapsarray_colsep_dim
+\dim_new:N \l_sdaps_sdapsarray_rowsep_dim
+
+\keys_define:nn { sdaps / sdapsarray }
+{
+  flip       .bool_set:N   = \l_sdaps_sdapsarray_flip_bool,
+  flip       .initial:n  = false,
+  flip       .default:n  = true,
+  layouter   .tl_set:N   = \l_sdaps_sdapsarray_layouter_tl,
+  layouter   .initial:n  = default,
+  align      .tl_set:N   = \l_sdaps_sdapsarray_align_tl,
+  align      .initial:n  = { },
+  keepenv    .bool_set:N   = \l_sdaps_sdapsarray_keepenv_bool,
+  keepenv    .initial:n  = false,
+  keepenv    .default:n  = true,
+
+  angle          .code:n     = {
+    \int_set:Nn \l_sdaps_sdapsarray_angle_int {#1}
+    \tl_set:Nx \l_sdaps_sdapsarray_angle_sine_tl { \fp_to_decimal:n {sind(#1)}}
+  },
+  angle          .initial:n  = 70,
+  colsep         .dim_set:N  = \l_sdaps_sdapsarray_colsep_dim,
+  colsep         .initial:n  = 6pt,
+  rowsep         .dim_set:N  = \l_sdaps_sdapsarray_rowsep_dim,
+  rowsep         .initial:n  = 0pt,
+}
+
+
+\cs_new_nopar:Nn \l_sdaps_sdapsarray_alignment_set_have_content:
+{
+  \bool_set_true:N\l__sdaps_sdapsarray_have_content_bool
+}
+
+\cs_new_nopar:Nn \_sdaps_sdapsarray_alignment: {
+    % End the last group, which will be the group that was begun earlier.
+    % If the earlier cell was the first one, then this egroup also starts the
+    % hbox to temporarily store the cells.
+  \egroup
+  \bool_if:NF \l__sdaps_sdapsarray_in_top_group_bool {
+    \msg_error:nnn { sdapsarray } { unmatched_grouping_level } { an~alignment~tab }
+  }
+
+  % We need to notify the outside scope that there are items, will be inserted
+  % multiple times, but that does not matter.
+  \group_insert_after:N\l_sdaps_sdapsarray_alignment_set_have_content:
+
+  % Just in case someone leaked a change into our scope
+  \bool_if:NF \l_sdaps_sdapsarray_keepenv_bool {
+    \cs_set_eq:NN \cr \_sdaps_sdapsarray_newline:
+    \cs_set_eq:NN \\ \cr
+  }
+
+  % Define a cell now, we can just put everything into a new cell, and that
+  % should work fine.
+  % Note that cells are not safe for fragile commands at the moment.
+  \_sdaps_sdapsarray_cell:w \bgroup
+    \bool_set_false:N \l__sdaps_sdapsarray_in_top_group_bool
+    \cs_set_eq:NN \\ \cr
+    \tex_ignorespaces:D
+}
+
+\msg_new:nnn { sdapsarray } { unmatched_grouping_level } { The~grouping~level~of~a~cell~was~not~even.~Please~ensure~all~braces~are~balanced~out!~This~error~occured~at~#1. }
+\msg_new:nnn { sdapsarray } { unequal_cols } { The~number~of~columns~is~not~equal~for~all~rows. }
+\msg_new:nnn { sdapsarray } { no_new_line_at_end } { You~have~terminated~the~last~line~with~\textbackslash\textbackslash~or~similar.~This~can~have~side~effects,~please~remove~it. }
+\msg_new:nnn { sdapsarray } { wrong_mode } { The~sdapsarray~environment~can~only~function~in~vertical~mode~(both~inner~and~outer). }
+
+\cs_new:Nn \_sdaps_sdapsarray_start_cells: {
+    \bool_if:NF \l__sdaps_sdapsarray_in_top_group_bool {
+      \msg_error:nnn { sdapsarray } { unmatched_grouping_level } { the~end~of~a~row~header }
+    }
+  \egroup
+
+  % We are in the environment scope again here
+
+  % Notify code that we are going to generate cells for a new row.
+  \sdaps_array_row_start:
+
+  % Start an hbox to stow away the cells.
+  % The rest of the setup happens in the alignment handler
+  \hbox_set:Nw \l_tmpb_box \bgroup
+    \group_insert_after:N \hbox_set_end:
+    \bool_set_true:N \l__sdaps_sdapsarray_in_top_group_bool
+}
+
+\cs_new_nopar:Nn \_sdaps_sdapsarray_linestart: {
+  \sdaps_array_assign_rowhead:Nw \l_tmpa_box
+    \bgroup
+      \cs_set_eq:NN \\ \cr
+
+      \bool_set_true:N \l__sdaps_sdapsarray_in_top_group_bool
+      \bgroup
+        \group_insert_after:N \_sdaps_sdapsarray_start_cells:
+        \bool_set_false:N \l__sdaps_sdapsarray_in_top_group_bool
+        % Ignore following spaces by the user
+        \tex_ignorespaces:D
+}
+
+\cs_new_nopar:Nn \_sdaps_sdapsarray_newline: {
+    \egroup
+    \bool_if:NF \l__sdaps_sdapsarray_in_top_group_bool {
+      \msg_error:nnn { sdapsarray } { unmatched_grouping_level } { the~end~of~a~row }
+    }
+  \egroup
+
+  % We are in the environment scope again here
+  % Output the last line if the cells were non-empty.
+  \bool_if:NT \l__sdaps_sdapsarray_have_content_bool {
+    \sdaps_array_row:NN \l_tmpa_box \l_tmpb_box
+  }
+  \bool_set_false:N \l__sdaps_sdapsarray_have_content_bool
+
+  \cs_set_eq:NN \_sdaps_sdapsarray_cell:w \sdaps_array_cell:w
+  \_sdaps_sdapsarray_linestart:
+}
+
+\cs_new:Npn\sdaps_array_nested_alignenv: {
+  \char_set_catcode_alignment:N &
+  \cs_set_eq:NN \cr \sdaps_orig_cr
+  \cs_set_eq:NN \\ \sdaps_orig_backslash
+}
+
+\cs_new:Npn\sdaps_array_nested_alignenv:w {
+  \bgroup
+    \sdaps_array_nested_alignenv:
+    % swallow group opening token
+    \tex_let:D\next=
+}
+\cs_new_eq:NN \sdapsnested \sdaps_array_nested_alignenv:w
+
+\group_begin:
+\char_set_catcode_active:N &
+\cs_new:Nn \_sdaps_sdapsarray_defines: {
+  \cs_set_eq:NN \sdaps_orig_cr \cr
+  \cs_set_eq:NN \sdaps_orig_backslash \\
+  \bool_if:NF \l_sdaps_sdapsarray_keepenv_bool {
+    \char_set_catcode_active:N &
+    \cs_set_eq:NN \cr \sdaps_array_newline:
+    \cs_set_eq:NN \\ \cr
+    \cs_set_eq:NN & \sdaps_array_alignment:
+  }
+}
+\group_end:
+
+%%%%%%
+%%%%%%
+
+
+\box_new:N \l_sdaps_sdapsarray_headers_box
+\box_new:N \l_sdaps_sdapsarray_boxlist_head_box
+\box_new:N \l_sdaps_sdapsarray_boxlist_tail_box
+
+\cs_new_protected:Nn \_sdaps_sdapsarray_prepend_box:NN {
+  \hbox_set:Nn #2 {
+    \box_use:N #1
+    \hbox_unpack:N #2
+  }
+  \box_clear:N #1
+}
+
+\cs_new_protected:Nn \_sdaps_sdapsarray_append_box:NN {
+  \hbox_set:Nn #2 {
+    \hbox_unpack:N #2
+    \box_use:N #1
+  }
+  \box_clear:N #1
+}
+
+\cs_new_protected:Nn \_sdaps_sdapsarray_pop_last_box:NN {
+  \hbox_set:Nn #2 {
+    \hbox_unpack:N #2
+    \box_gset_to_last:N \g_tmpa_box
+  }
+  \box_set_eq:NN #1 \g_tmpa_box
+  \box_gclear:N \g_tmpa_box
+}
+
+\cs_new_protected:Nn \_sdaps_sdapsarray_pop_last_hbox_unpack:NN {
+  \_sdaps_sdapsarray_pop_last_box:NN #1 #2
+  \hbox_set:Nn #1 {
+    \hbox_unpack:N #1
+    \box_gset_to_last:N \g_tmpa_box
+  }
+  \box_set_eq:NN #1 \g_tmpa_box
+  \box_gclear:N \g_tmpa_box
+}
+
+\cs_new_protected:Nn \_sdaps_sdapsarray_boxlist_void_if_empty:N {
+  \hbox_set:Nn #1 {
+    \hbox_unpack:N #1
+    \box_set_to_last:N #1
+    \box_if_empty:NTF #1 {
+      \bool_gset_true:N \g_tmpa_bool
+    } {
+      \box_use:N #1
+      \bool_gset_false:N \g_tmpa_bool
+    }
+  }
+  \bool_if:NT \g_tmpa_bool {
+    \box_clear:N #1
+  }
+}
+
+
+\cs_new_protected_nopar:Nn \_sdaps_sdapsarray_alignment_flip:
+{
+  \_sdaps_sdapsarray_end_cell_flip:
+
+  % Just in case someone leaked a change into our scope
+  \bool_if:NF \l_sdaps_sdapsarray_keepenv_bool {
+    \cs_set_eq:NN \cr \_sdaps_sdapsarray_newline_flip:
+    \cs_set_eq:NN \\ \cr
+  }
+
+  % Next up is either a cell or a row header. We can figure that out by checking
+  % that the row headings box is void
+  \box_if_empty:NTF \l_sdaps_sdapsarray_headers_box {
+    \sdaps_array_assign_rowhead:Nw \l_tmpa_box \bgroup
+      \bool_set_false:N \l__sdaps_sdapsarray_in_top_group_bool
+      \cs_set_eq:NN \\ \cr
+      % Ignore following spaces by the user
+      \tex_ignorespaces:D
+  } {
+    \sdaps_array_assign_cell:Nw \l_tmpa_box \bgroup
+      \bool_set_false:N \l__sdaps_sdapsarray_in_top_group_bool
+      \cs_set_eq:NN \\ \cr
+      \tex_ignorespaces:D
+  }
+}
+
+\cs_new_nopar:Nn \_sdaps_sdapsarray_end_cell_flip: {
+  \egroup % Finish of the cell
+  \bool_if:NF \l__sdaps_sdapsarray_in_top_group_bool {
+    \msg_error:nnn { sdapsarray } { unmatched_grouping_level } { end~of~cell~or~row }
+  }
+
+  % Get last box from head
+  \_sdaps_sdapsarray_pop_last_box:NN \l_tmpb_box \l_sdaps_sdapsarray_boxlist_head_box
+  % Append the new box to the list of boxes for this row
+  \_sdaps_sdapsarray_append_box:NN \l_tmpa_box \l_tmpb_box
+  % Prepend the new box to the tail
+  \_sdaps_sdapsarray_prepend_box:NN \l_tmpb_box \l_sdaps_sdapsarray_boxlist_tail_box
+}
+
+\cs_new_nopar:Nn \_sdaps_sdapsarray_end_line_flip: {
+  % At the end of the line, move tail into head.
+  % First check that head is empty.
+  \_sdaps_sdapsarray_boxlist_void_if_empty:N \l_sdaps_sdapsarray_boxlist_head_box
+  \box_if_empty:NF \l_sdaps_sdapsarray_boxlist_head_box {
+    \msg_error:nn { sdapsarray } { unequal_cols }
+  }
+  \box_set_eq:NN \l_sdaps_sdapsarray_boxlist_head_box \l_sdaps_sdapsarray_boxlist_tail_box
+  \box_clear:N \l_sdaps_sdapsarray_boxlist_tail_box
+
+  % If this was the first row, store it away, these are the headings.
+  \box_if_empty:NT \l_sdaps_sdapsarray_headers_box {
+    \box_set_eq:NN \l_sdaps_sdapsarray_headers_box \l_sdaps_sdapsarray_boxlist_head_box
+    \box_clear:N \l_sdaps_sdapsarray_boxlist_head_box
+  }
+}
+
+\cs_new_nopar:Nn \_sdaps_sdapsarray_newline_flip: {
+  \_sdaps_sdapsarray_end_cell_flip:
+  \_sdaps_sdapsarray_end_line_flip:
+
+  % Create next box to store away, this has to be a colhead at this point
+  \sdaps_array_assign_colhead:Nw \l_tmpa_box \bgroup
+    \bool_set_false:N \l__sdaps_sdapsarray_in_top_group_bool
+    \cs_set_eq:NN \\ \cr
+    \tex_ignorespaces:D
+}
+
+\NewDocumentEnvironment { sdapsarray } { o }
+{
+  \bool_set_false:N \l__sdaps_sdapsarray_in_top_group_bool
+  \group_begin:
+
+    \tl_set:Nn \l_tmpa_tl { }
+    \IfNoValueF { #1 } {
+      \tl_set:Nn \l_tmpa_tl { #1 }
+    }
+
+    \box_clear:N \l_tmpa_box
+    \box_clear:N \l_tmpb_box
+
+    % Ensure vertical mode.
+    \tex_par:D
+    \if_mode_vertical:
+    \else:
+      \msg_error:nn { sdapsarray } { wrong_mode }
+    \fi
+
+    % This needs to be initialized here as otherwise the values would be
+    % expanded at import time.
+    \keys_set:nV { sdaps / sdapsarray } \l_tmpa_tl
+
+    \sdaps_array_begin:VV \l_sdaps_sdapsarray_layouter_tl \l_sdaps_sdapsarray_align_tl
+
+    % Note, this environment is fragile; we redefine & to be active.
+    % One can go back into normal mode by using \sdapsnested{} though.
+
+    \bool_if:NTF \l_sdaps_sdapsarray_flip_bool {
+      \cs_set_eq:NN \sdaps_array_newline: \_sdaps_sdapsarray_newline_flip:
+      \cs_set_eq:NN \sdaps_array_alignment: \_sdaps_sdapsarray_alignment_flip:
+
+      \_sdaps_sdapsarray_defines:
+
+      % Two hboxes to hold the content, note that
+      % a: row headers, b: cells/col headers
+      \box_clear:N \l_sdaps_sdapsarray_headers_box
+      \box_clear:N \l_sdaps_sdapsarray_boxlist_head_box
+      \box_clear:N \l_sdaps_sdapsarray_boxlist_tail_box
+
+      % not sure why we need this group, but nothing works without it
+      \bgroup
+        % This is a bit creative to say the least
+        \sdaps_array_row_start:
+
+        \bool_set_true:N \l__sdaps_sdapsarray_in_top_group_bool
+        \sdaps_array_assign_rowhead:Nw \l_tmpa_box \bgroup
+          \bool_set_false:N \l__sdaps_sdapsarray_in_top_group_bool
+          \cs_set_eq:NN \\ \cr
+          % Ignore following spaces by the user
+          \tex_ignorespaces:D
+    } {
+      \cs_set_eq:NN \sdaps_array_newline: \_sdaps_sdapsarray_newline:
+      \cs_set_eq:NN \sdaps_array_alignment: \_sdaps_sdapsarray_alignment:
+      \_sdaps_sdapsarray_defines:
+
+      \cs_set_eq:NN \_sdaps_sdapsarray_cell:w \sdaps_array_colhead:w
+      \_sdaps_sdapsarray_linestart:
+    }
+
+  % If we redefine &, then the next character might have the wrong catcode
+  % (i.e. it could still be an alignment character). Execute the alignment
+  % code directly if the next character is &.
+  \bool_if:NF \l_sdaps_sdapsarray_keepenv_bool {
+    \peek_charcode_remove:NT & { \sdaps_array_alignment: }
+  }
+}
+{
+    \bool_if:NTF \l_sdaps_sdapsarray_flip_bool {
+        \_sdaps_sdapsarray_end_cell_flip:
+
+        % At this point we should have swallowed all items from the head list.
+        % If not, then someone likely add a stray \\ command or similar
+        \_sdaps_sdapsarray_boxlist_void_if_empty:N \l_sdaps_sdapsarray_boxlist_head_box
+        \box_if_empty:NTF \l_sdaps_sdapsarray_boxlist_head_box {
+          \_sdaps_sdapsarray_end_line_flip:
+        } {
+          \msg_error:nn { sdapsarray } { no_new_line_at_end }
+        }
+
+        % Now we can have fun!
+        % Pop cells and heading, until we cannot find any new ones.
+        \_sdaps_sdapsarray_pop_last_hbox_unpack:NN \l_tmpa_box \l_sdaps_sdapsarray_headers_box
+        \_sdaps_sdapsarray_pop_last_box:NN \l_tmpb_box \l_sdaps_sdapsarray_boxlist_head_box
+        \bool_do_while:nn { ! \box_if_empty_p:N \l_tmpa_box || ! \box_if_empty_p:N \l_tmpb_box } {
+          \sdaps_array_row:NN \l_tmpa_box \l_tmpb_box
+
+          \sdaps_array_row_start:
+
+          \_sdaps_sdapsarray_pop_last_hbox_unpack:NN \l_tmpa_box \l_sdaps_sdapsarray_headers_box
+          \_sdaps_sdapsarray_pop_last_box:NN \l_tmpb_box \l_sdaps_sdapsarray_boxlist_head_box
+        }
+
+      \egroup
+    } {
+        \egroup
+
+        \bool_if:NF \l__sdaps_sdapsarray_in_top_group_bool {
+          \msg_error:nnn { sdapsarray } { unmatched_grouping_level } { the~end~of~the~environment }
+        }
+      \egroup
+
+      % We are in the environment scope again here
+      % Output the last line if the cells were non-empty.
+      \bool_if:NT \l__sdaps_sdapsarray_have_content_bool {
+        \sdaps_array_row:NN \l_tmpa_box \l_tmpb_box
+      }
+    }
+
+    \sdaps_array_end:
+
+  \group_end:
+}
+
+\ExplSyntaxOff
+
+
+\endinput
+%%
+%% End of file `sdapsarray.sty'.
-- 
cgit v1.2.3

