diff options
Diffstat (limited to 'macros/latex/contrib/sdaps/sdapsarray.dtx')
-rw-r--r-- | macros/latex/contrib/sdaps/sdapsarray.dtx | 1167 |
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 |