summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/sdaps/sdapsarray.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/sdaps/sdapsarray.dtx')
-rw-r--r--macros/latex/contrib/sdaps/sdapsarray.dtx1167
1 files changed, 1167 insertions, 0 deletions
diff --git a/macros/latex/contrib/sdaps/sdapsarray.dtx b/macros/latex/contrib/sdaps/sdapsarray.dtx
new file mode 100644
index 0000000000..185fc9b961
--- /dev/null
+++ b/macros/latex/contrib/sdaps/sdapsarray.dtx
@@ -0,0 +1,1167 @@
+% \iffalse meta-comment
+%
+% 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.
+%
+% This work has the LPPL maintenance status `maintained'.
+%
+% The Current Maintainer of this work is Benjamin Berg.
+%
+% \fi
+%
+% \iffalse
+%<*driver>
+\ProvidesFile{sdapsarray.dtx}
+%</driver>
+%<package>\NeedsTeXFormat{LaTeX2e}[1999/12/01]
+%<package>\ProvidesPackage{sdapsarray}
+%<*package>
+ [2015/04/10 v0.1 Initial version of SDAPS array package]
+%</package>
+%
+%<*driver>
+\documentclass{ltxdoc}
+\usepackage{sdapsarray}[2015/04/10]
+%\EnableCrossrefs
+\CodelineIndex
+\RecordChanges
+\begin{document}
+ \DocInput{sdapsarray.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \CheckSum{0}
+%
+% \CharacterTable
+% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
+% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
+% Digits \0\1\2\3\4\5\6\7\8\9
+% Exclamation \! Double quote \" Hash (number) \#
+% Dollar \$ Percent \% Ampersand \&
+% Acute accent \' Left paren \( Right paren \)
+% Asterisk \* Plus \+ Comma \,
+% Minus \- Point \. Solidus \/
+% Colon \: Semicolon \; Less than \<
+% Equals \= Greater than \> Question mark \?
+% Commercial at \@ Left bracket \[ Backslash \\
+% Right bracket \] Circumflex \^ Underscore \_
+% Grave accent \` Left brace \{ Vertical bar \|
+% Right brace \} Tilde \~}
+%
+%
+% \changes{v0.1}{2015/01/14}{Initial version}
+%
+% \GetFileInfo{sdapsarray.dtx}
+%
+% \DoNotIndex{\newcommand,\newenvironment}
+%
+%
+% \title{The \textsf{sdapsarray} package\thanks{This document
+% corresponds to \textsf{sdapsarray}~\fileversion, dated \filedate.}}
+% \author{Benjamin Berg \\ \texttt{benjamin@sipsolutions.net}}
+%
+% \maketitle
+%
+% \section{Documentation}
+%
+% Please refer to \url{https://sdaps.org/class-doc} for documentation.
+%
+% \StopEventually{\PrintChanges\PrintIndex}
+%
+% \section{Implementation}
+%
+% This package uses the \LaTeX3 language internally, so we need to enable it.
+% \begin{macrocode}
+% We need at least 2011-08-23 for \keys_set_known:nnN
+\RequirePackage{expl3}[2011/08/23]
+%\RequirePackage{xparse}
+\ExplSyntaxOn
+% \end{macrocode}
+%
+% And we need a number of other packages.
+% \begin{macrocode}
+\ExplSyntaxOff
+
+\RequirePackage{xparse}
+\RequirePackage{sdapsbase}
+
+
+\ExplSyntaxOn
+
+% \end{macrocode}
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% \subsection{Tempfile handling}
+%
+% This is a bit weird, but under some conditions we need extended information
+% about each row in the document (for page break detection). As it makes little
+% to no sense to load all this information into memory at start we use two
+% temporary files instead. As these files should not change their content
+% for reruns (that would break e.g. latexmk) we copy the "tic" file into "toc"
+% so that "toc" can be read while "tic" is being re-written.
+% We then go on to define a macro which will read a single
+% line and return true if it is different from the previous line.
+% This is the indicator that a page/column break has happened and the
+% row header needs to be inserted.
+%
+% \begin{macrocode}
+
+% This will change in early 2018, but the new code apparently does not
+% provide the old name of the definition.
+% i.e. this is a bad hack and should be removed latest in 2019 or so
+\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
+ }
+}
+
+% \end{macrocode}
+%
+% \subsection{Initialization}
+%
+% Global definitions and penalty constant.
+%
+% \begin{macrocode}
+
+\prop_new:N \g__sdaps_array_layouter_prop
+
+% XXX: Penalty in between rows. After the header a nobreak is inserted, but
+% do we want special penalties elsewhere (preventing orphans/widows?)
+\int_new:N \g_sdaps_array_row_penalty_tl
+\int_gset:Nn \g_sdaps_array_row_penalty_tl { 10 }
+
+% \end{macrocode}
+%
+% \subsection{Initialization}
+%
+% Define some routines to store width information for columns (across builds).
+%
+% \begin{macrocode}
+
+\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}}
+ }
+}
+
+% Define for loading sdaps code in aux file
+\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
+
+% Define for loading sdaps code in aux file
+\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
+
+% \end{macrocode}
+%
+% \subsection{Array Layouter}
+%
+% \subsubsection{User facing macros}
+%
+% \begin{macrocode}
+
+\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=
+}
+
+% XXX: Could this live in local scope instead?
+\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
+}
+
+% \end{macrocode}
+%
+% \subsubsection{Common Layouter Macros}
+%
+% \begin{macrocode}
+
+\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:
+}
+
+% Only sane for header row
+\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=
+}
+
+% XXX: A parbox layouter with fixed width would be nice
+%\cs_new_protected_nopar:Nn \sdaps_array_cell_fixed:n {}
+
+
+\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: },
+}
+
+% \end{macrocode}
+%
+% \subsection{Exporting a tabular/array like environment}
+%
+% \subsubsection{Helper required for the environment}
+%
+% \begin{macrocode}
+
+\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,
+}
+
+
+% \end{macrocode}
+%
+% \subsubsection{Environment definition}
+%
+% \begin{macrocode}
+
+\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:
+
+%%%%%%
+% Flipping environment
+%%%%%%
+
+% First some helpers
+
+\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
+ }
+}
+
+% Lets say we are in row 4, cell 2 as defined in the environment, so the actual
+% position is 2, 4. Minus the row headers, this gives us 2, 3.
+% This means we need to append the cell to the box 3rd last box.
+% In that case we have to append the cell
+
+\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
+
+%
+% \end{macrocode}
+%
+
+% \Finale
+\endinput