summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/topiclongtable/topiclongtable.sty
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/topiclongtable/topiclongtable.sty')
-rw-r--r--macros/latex/contrib/topiclongtable/topiclongtable.sty433
1 files changed, 433 insertions, 0 deletions
diff --git a/macros/latex/contrib/topiclongtable/topiclongtable.sty b/macros/latex/contrib/topiclongtable/topiclongtable.sty
new file mode 100644
index 0000000000..d75295cdd5
--- /dev/null
+++ b/macros/latex/contrib/topiclongtable/topiclongtable.sty
@@ -0,0 +1,433 @@
+%
+% This work is sponsored by Human Predictions, LLC (<http://www.humanpredictions.com>).
+%
+% This work is maintained by Paolo Brasolin (<paolo.brasolin@gmail.com>).
+%
+% This work is licensed under MIT License.
+%
+% This work is a LaTeX package consisting of the following files:
+% * README.md
+% * topiclongtable.sty
+% * topiclongtable-doc.tex
+% * topiclongtable-doc.pdf
+%
+\NeedsTeXFormat{LaTeX2e}[2017-04-15]
+
+\RequirePackage{zref-abspage}[2016/05/21]
+\RequirePackage{xparse}[2017/11/14]
+\RequirePackage{expl3}[2017/11/14]
+\RequirePackage{array}[2016/10/06]
+\RequirePackage{multirow}[2016/11/25]
+\RequirePackage{longtable}[2014/10/28]
+\PassOptionsToPackage{longtable}{multirow}
+
+\ProvidesExplPackage {topiclongtable} {2019/07/09} {1.3.1} {Renders autocollapsing cells in longtables}
+
+\ProcessOptions\relax
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% topics bookeeping (current table)
+\seq_new:N \g_tlt_topics_stack_seq
+\prop_new:N \g_tlt_topics_labels_prop
+
+%% tracks row spans of topics (for computation)
+\prop_new:N \g_tlt_rows_spans_prop
+%% holds computed multirow heights (for rendering)
+\clist_new:N \g_tlt_multirows_heights_clist
+
+%% holds dumped data
+\prop_new:N \g_tlt_raw_data_prop
+%% holds dumped data (parsed for eas of access)
+\prop_new:N \g_tlt_loaded_data_prop
+
+%% hold obvious col/row indices
+\int_new:N \g_tlt_row_idx_int
+\int_new:N \g_tlt_col_idx_int
+\int_new:N \g_tlt_col_tot_int
+
+%% io stream
+\iow_new:N \g_tlt_stream_iow
+
+%% tracks table index (id)
+\tl_new:N \g_tlt_cur_table_id_tl
+
+%% hold continuation code
+\tl_new:N \g_tlt_continuation_code_tl
+
+%% hold multirow options
+\tl_new:N \g_tlt_multirow_vpos_tl
+\tl_new:N \g_tlt_multirow_width_tl
+%% whose defaults are
+\tl_gset:Nn \g_tlt_multirow_vpos_tl {t}
+\tl_gset:Nn \g_tlt_multirow_width_tl {*}
+
+%% just a handy helper
+\cs_generate_variant:Nn \prop_item:Nn { NV }
+
+%% INIT PROCEDURES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% initialize the document
+\cs_new:Nn \tlt_init: {
+ %% load computed data if it exists
+ \tl_set:Nn \l_tmpa_tl { \c_sys_jobname_str.tlt }
+ \file_if_exist:nTF { \l_tmpa_tl } {
+ \ior_open:Nn \g_tlt_stream_iow { \l_tmpa_tl }
+ \ior_map_inline:Nn \g_tlt_stream_iow { ##1 }
+ \ior_close:N \g_tlt_stream_iow
+ } {
+ %% file was not found - that's ok
+ }
+ %% leave an open stream to dump computed data
+ \iow_open:Nn \g_tlt_stream_iow { \c_sys_jobname_str.tlt }
+}
+
+%% initialize a table with given id
+\cs_new:Nn \tlt_init_table:n {
+ %% fetch serialized data (clist of clists) if found,
+ %% initialize an empty list otherwise
+ %% NOTE: \g_tlt_raw_data_prop is defined in the (generated) *.tlt file
+ \prop_get:NnNTF \g_tlt_raw_data_prop { #1 } \l_tmpa_tl {
+ \clist_set:NV \l_tmpa_clist \l_tmpa_tl
+ } {
+ \clist_set_eq:NN \l_tmpa_clist \c_empty_clist
+ }
+ %% transform clist into indexed prop for ease of access
+ \int_zero:N \l_tmpa_int
+ \clist_map_inline:Nn \l_tmpa_clist {
+ \int_incr:N \l_tmpa_int
+ \prop_gput:NVn \g_tlt_loaded_data_prop \l_tmpa_int { ##1 }
+ }
+ %% reset counters and table variables
+ \int_gzero:N \g_tlt_col_idx_int
+ \int_gzero:N \g_tlt_row_idx_int
+ \prop_gclear:N \g_tlt_topics_labels_prop
+ \clist_gset:Nn \g_tlt_multirows_heights_clist \c_empty_clist
+ \clist_gset:Nn \g_tlt_rows_spans_prop \c_empty_clist
+}
+
+\cs_generate_variant:Nn \tlt_init_table:n { V }
+
+%% EXIT PROCEDURES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% we need to load data through an ordinary macro as at load time we won't have expl3
+\DeclareExpandableDocumentCommand{\TltSetDataRow}{mm}{
+ \prop_gput:Nnn \g_tlt_raw_data_prop { #1 }{ #2 }
+}
+
+%% finalize a given table
+\cs_new:Nn \tlt_exit_table:n {
+ %% compute table data
+ \tlt_compute_data:
+ %% dump computed data
+ \iow_now:Nx \g_tlt_stream_iow {
+ \noexpand\TltSetDataRow { #1 }{ \g_tlt_multirows_heights_clist }
+ }
+}
+
+\cs_generate_variant:Nn \tlt_exit_table:n { V }
+
+%% finalize document
+\cs_new:Nn \tlt_exit: {
+ %% close the output stream
+ \iow_close:N \g_tlt_stream_iow
+}
+
+%% MULTIROW HEIGHT FETCHING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\cs_new:Nn \tlt_calc_cell_height:n {
+ %% get loaded data for current column
+ \prop_get:NVNTF \g_tlt_loaded_data_prop \g_tlt_col_idx_int \l_tmpa_tl {
+ \clist_set:NV \l_tmpa_clist \l_tmpa_tl
+ } {
+ \clist_clear:N \l_tmpa_clist
+ }
+ %% get the first element (if present) and 1 otherwise
+ \clist_pop:NNTF \l_tmpa_clist \l_tmpb_tl {} { \tl_set:Nn \l_tmpb_tl { 1 } }
+ \prop_gput:NVV \g_tlt_loaded_data_prop \g_tlt_col_idx_int \l_tmpa_clist
+ \int_set:Nn { #1 } \l_tmpb_tl
+}
+
+%% MULTIROW CONFIGURATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\NewDocumentCommand{\TopicSetVPos}{m}{
+ \tl_gset:Nn \g_tlt_multirow_vpos_tl {#1}
+}
+
+\NewDocumentCommand{\TopicSetWidth}{m}{
+ \tl_gset:Nn \g_tlt_multirow_width_tl {#1}
+}
+
+%% TOPICS BOOKKEEPING AND RENDERING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\NewDocumentCommand{\TopicSetContinuationCode}{m}{
+ \tl_gset:Nn \g_tlt_continuation_code_tl {#1}
+}
+
+%% internal used to mark/render new topic
+\cs_new:Nn \mark_new_topic:nn {
+ % reset stack up until #1
+ \seq_if_in:NnTF \g_tlt_topics_stack_seq {#1} {
+ \bool_do_until:Nn \l_tmpa_bool {
+ \seq_gpop:NN \g_tlt_topics_stack_seq \l_tmpa_tl
+ \prop_gpop:NoN \g_tlt_topics_labels_prop \l_tmpa_tl \l_tmpb_tl
+ \tl_set:Nn \l_tmpb_tl {#1}
+ \bool_gset:Nn \l_tmpa_bool { \tl_if_eq_p:NN \l_tmpa_tl \l_tmpb_tl }
+ }
+ } {}
+ \prop_get:NVNTF \g_tlt_rows_spans_prop \g_tlt_col_idx_int \l_tmpa_tl {
+ \clist_set:NV \l_tmpa_clist \l_tmpa_tl
+ } {
+ \clist_clear:N \l_tmpa_clist
+ }
+ \clist_push:Nn \l_tmpa_clist { 1 }
+ \prop_gput:NVV \g_tlt_rows_spans_prop \g_tlt_col_idx_int \l_tmpa_clist
+ \tlt_calc_cell_height:n \l_tmpb_int
+ \seq_gpush:Nn \g_tlt_topics_stack_seq {#1}
+ \prop_gput:Nnn \g_tlt_topics_labels_prop {#1} {#2}
+ % render the multirow
+ \multirow[\tl_use:N \g_tlt_multirow_vpos_tl]{\int_use:N \l_tmpb_int}{\tl_use:N \g_tlt_multirow_width_tl}{\prop_item:Nn \g_tlt_topics_labels_prop {#1}}
+}
+
+%% internal used to mark/render reappearing topic
+\cs_new:Nn \mark_old_topic: {
+ \prop_get:NVNTF \g_tlt_rows_spans_prop \g_tlt_col_idx_int \l_tmpa_tl {
+ \clist_set:NV \l_tmpa_clist \l_tmpa_tl
+ } {
+ \ERROR %% unreachable by user by definition
+ }
+
+ \on_first_row_of_page:N \l_tmpa_bool
+ \bool_if:NTF \l_tmpa_bool {
+ \prop_get:NVNTF \g_tlt_rows_spans_prop \g_tlt_col_idx_int \l_tmpa_tl {
+ \clist_set:NV \l_tmpa_clist \l_tmpa_tl
+ } {
+ \clist_clear:N \l_tmpa_clist
+ }
+ \clist_push:Nn \l_tmpa_clist { 1 }
+ \prop_gput:NVV \g_tlt_rows_spans_prop \g_tlt_col_idx_int \l_tmpa_clist
+ \tlt_calc_cell_height:n \l_tmpb_int
+ %% \seq_gpush:Nn \g_tlt_topics_stack_seq {#1}
+ %% \prop_gput:Nnn \g_tlt_topics_labels_prop {#1} {#2}
+ %% render the multirow cell
+ %% \int_use:N \l_tmpb_int
+ % render multirow
+ \multirow[\tl_use:N \g_tlt_multirow_vpos_tl]{\int_use:N \l_tmpb_int}{\tl_use:N \g_tlt_multirow_width_tl}{\prop_item:NV \g_tlt_topics_labels_prop \g_tlt_col_idx_int \tl_use:N \g_tlt_continuation_code_tl}
+ %% \clist_pop:NN \l_tmpa_clist \l_tmpa_tl
+ %% \int_set:Nn \l_tmpa_int \l_tmpa_tl
+ %% \int_incr:N \l_tmpa_int
+ %% \clist_push:NV \l_tmpa_clist \l_tmpa_int
+ %% \prop_gput:NVV \g_tlt_rows_spans_prop \g_tlt_col_idx_int \l_tmpa_clist
+ %% \tlt_calc_cell_height:n \l_tmpb_int
+ } {
+ \clist_pop:NN \l_tmpa_clist \l_tmpa_tl
+ \int_set:Nn \l_tmpa_int \l_tmpa_tl
+ \int_incr:N \l_tmpa_int
+ \clist_push:NV \l_tmpa_clist \l_tmpa_int
+ \prop_gput:NVV \g_tlt_rows_spans_prop \g_tlt_col_idx_int \l_tmpa_clist
+ \tlt_calc_cell_height:n \l_tmpb_int
+ % render nothing
+ }
+}
+
+%% HIGH LEVEL GET/SET TOPICS OPERATIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\msg_new:nnn {topiclongtable} {invalid-index} {
+ You~tried~to~use~an~uninitialized~topic~index~\msg_line_context:.
+}
+
+% together, the two macros compose the high-level \Topic command
+
+\cs_new:Nn \tlt_set_topic:nn {
+ \tl_set:Nn \l_tmpa_tl { #2 }
+ \tl_set:Nx \l_tmpb_tl { \prop_item:Nn \g_tlt_topics_labels_prop { #1 } }
+ \tl_if_eq:NNTF \l_tmpa_tl \l_tmpb_tl
+ { \tlt_get_topic:n { #1 } }
+ { \mark_new_topic:nn {#1} {#2} }
+}
+
+\cs_new:Nn \tlt_get_topic:n {
+ \seq_if_in:NnTF \g_tlt_topics_stack_seq {#1} {
+ \mark_old_topic:
+ } {
+ \msg_error:nn {topiclongtable} {invalid-index}
+ }
+}
+
+\cs_generate_variant:Nn \tlt_get_topic:n { V }
+\cs_generate_variant:Nn \tlt_set_topic:nn { Vn }
+
+%% HEIGHTS COMPUTATIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\cs_new:Nn \tlt_compute_data: {
+ \clist_gclear:N \g_tlt_multirows_heights_clist
+ %% iterate over column indices; idx still has the last (maximum) value
+ \int_step_inline:nnnn { 1 } { 1 } { \g_tlt_col_idx_int } {
+ %% fetch memorized cell spans, ordered from bottom to top
+ \prop_get:NnNTF \g_tlt_rows_spans_prop { ##1 } \l_tmpb_tl {
+ \clist_set:NV \l_tmpa_clist \l_tmpb_tl
+ } {
+ \clist_set:NV \l_tmpa_clist \c_empty_clist
+ }
+ \clist_reverse:N \l_tmpa_clist
+ %% empty tmp list (for outer step cycle)
+ \clist_clear:N \l_tmpb_clist
+ %% iterate over cell spans
+ \clist_map_inline:Nn \l_tmpa_clist {
+ % store column index
+ \int_set:Nn \l_tmpa_int { ####1 }
+ % push full height (n) for first cell of multirow
+ \clist_push:NV \l_tmpb_clist \l_tmpa_int
+ % push n-1 zeroes for the other cells spanned by the multirow
+ \int_while_do:nNnn { \l_tmpa_int } { > } { 1 } {
+ \clist_push:Nn \l_tmpb_clist { 0 }
+ \int_decr:N \l_tmpa_int
+ }
+ }
+ \clist_reverse:N \l_tmpb_clist
+ \clist_gput_right:Nx \g_tlt_multirows_heights_clist {{\l_tmpb_clist}}
+ }
+}
+
+%% CONDITIONALS ON ROW POSITIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\cs_new:Nn \is_last_row_of_page:n {
+ % get page of given row
+ \int_set:Nn \l_tmpa_int { #1 }
+ \tl_set:Nx \l_tmpa_tl {TLT-\theLT@tables-\int_use:N \l_tmpa_int}
+ \tl_set:Nx \l_tmpa_tl {\expandafter\zref@extract{\tl_use:N \l_tmpa_tl}{abspage}}
+ % get page of next row
+ \int_incr:N \l_tmpa_int
+ \tl_set:Nx \l_tmpb_tl {TLT-\theLT@tables-\int_use:N \l_tmpa_int}
+ \tl_set:Nx \l_tmpb_tl {\expandafter\zref@extract{\tl_use:N \l_tmpb_tl}{abspage}}
+ % true if different
+ \bool_not_p:n { \tl_if_eq_p:NN \l_tmpa_tl \l_tmpb_tl }
+}
+
+\cs_new:Nn \is_first_row_of_page:n {
+ % get page of given row
+ \int_set:Nn \l_tmpa_int { #1 }
+ \tl_set:Nx \l_tmpa_tl {TLT-\theLT@tables-\int_use:N \l_tmpa_int}
+ \tl_set:Nx \l_tmpa_tl {\expandafter\zref@extract{\tl_use:N \l_tmpa_tl}{abspage}}
+ % get page of prev row
+ \int_decr:N \l_tmpa_int
+ \tl_set:Nx \l_tmpb_tl {TLT-\theLT@tables-\int_use:N \l_tmpa_int}
+ \tl_set:Nx \l_tmpb_tl {\expandafter\zref@extract{\tl_use:N \l_tmpb_tl}{abspage}}
+ % true if different
+ \bool_set:Nn \l_tmpa_bool { \bool_not_p:n { \tl_if_eq_p:NN \l_tmpa_tl \l_tmpb_tl } }
+ \l_tmpa_bool
+}
+
+\cs_new:Nn \on_first_row_of_page:N {
+ % get page of given row
+ \int_set_eq:NN \l_tmpa_int \g_tlt_row_idx_int
+ \tl_set:Nx \l_tmpa_tl {TLT-\theLT@tables-\int_use:N \l_tmpa_int}
+ \tl_set:Nx \l_tmpa_tl {\expandafter\zref@extract{\tl_use:N \l_tmpa_tl}{abspage}}
+ % get page of prev row
+ \int_decr:N \l_tmpa_int
+ \tl_set:Nx \l_tmpb_tl {TLT-\theLT@tables-\int_use:N \l_tmpa_int}
+ \tl_set:Nx \l_tmpb_tl {\expandafter\zref@extract{\tl_use:N \l_tmpb_tl}{abspage}}
+ % true if different
+ \bool_set:Nn { #1 } { \bool_not_p:n { \tl_if_eq_p:NN \l_tmpa_tl \l_tmpb_tl } }
+}
+
+\cs_new:Nn \on_last_row_of_page:N {
+ % get page of given row
+ \int_set_eq:NN \l_tmpa_int \g_tlt_row_idx_int
+ \tl_set:Nx \l_tmpa_tl {TLT-\theLT@tables-\int_use:N \l_tmpa_int}
+ \tl_set:Nx \l_tmpa_tl {\expandafter\zref@extract{\tl_use:N \l_tmpa_tl}{abspage}}
+ % get page of prev row
+ \int_incr:N \l_tmpa_int
+ \tl_set:Nx \l_tmpb_tl {TLT-\theLT@tables-\int_use:N \l_tmpa_int}
+ \tl_set:Nx \l_tmpb_tl {\expandafter\zref@extract{\tl_use:N \l_tmpb_tl}{abspage}}
+ % true if different
+ \bool_set:Nn { #1 } { \bool_not_p:n { \tl_if_eq_p:NN \l_tmpa_tl \l_tmpb_tl } }
+}
+
+\cs_generate_variant:Nn \is_first_row_of_page:n { V }
+\cs_generate_variant:Nn \is_last_row_of_page:n { V }
+
+%% LINE DRAWING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\cs_new_protected:Nn \CLINE_split:n {
+ \clist_clear:N \l_tmpb_clist
+ \prop_map_inline:Nn \g_tlt_loaded_data_prop {
+ \tl_set:Nn \l_tmpa_tl {##1}
+ % get first element as integer A
+ \clist_set:Nn \l_tmpa_clist {##2}
+ \clist_pop:NNTF \l_tmpa_clist \l_tmpb_tl {
+ \int_set:Nn \l_tmpa_int \l_tmpb_tl
+ } {
+ \int_set_eq:NN \l_tmpa_int \c_one
+ }
+ % if integer A is zero, skip
+ \int_compare:nNnTF { \l_tmpa_int } { = } { 0 } {} {
+ \clist_gpush:NV \l_tmpb_clist \l_tmpa_tl
+ }
+ }
+ \clist_sort:Nn \l_tmpb_clist {
+ \int_compare:nNnTF { ##1 } { > } { ##2 } { \sort_return_swapped: } { \sort_return_same: }
+ }
+ \clist_pop:NNTF \l_tmpb_clist \l_tmpb_tl {} { \tl_set:Nn \l_tmpb_tl { #1 } }
+
+ \tl_gset:Nx \g_tmpa_tl { \noexpand\cline{\l_tmpb_tl-#1} }
+
+ \on_last_row_of_page:N \l_tmpa_bool
+ % note the tricky post-group expansion
+ \bool_if:NTF \l_tmpa_bool {} { \group_insert_after:N \g_tmpa_tl }
+}
+
+\cs_generate_variant:Nn \CLINE_split:n { V }
+
+%% USER INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% used on the first (F) and other (T) columns, hook in the column counter
+\newcolumntype{F}{>{\int_gset:Nn \g_tlt_col_idx_int {1}}}
+\newcolumntype{T}{>{\int_gincr:N \g_tlt_col_idx_int}}
+
+%% draws the separators, handles the row counter and labels to track pagebreaks
+\DeclareExpandableDocumentCommand{\TopicLine}{}{
+ \noalign{ \CLINE_split:V \g_tlt_col_tot_int }
+ \int_gincr:N \g_tlt_row_idx_int
+ \tl_set:Nx \l_tmpa_tl {TLT-\theLT@tables-\int_use:N \g_tlt_row_idx_int}
+ \expandafter\zref@labelbyprops{\tl_use:N \l_tmpa_tl}{abspage}
+}
+
+%% the main environment
+\NewDocumentEnvironment {topiclongtable} {m} {%
+ %% set next table id
+ \int_set_eq:NN \l_tmpa_int \theLT@tables
+ \int_incr:N \l_tmpa_int
+ \tl_gset:Nx \g_tlt_cur_table_id_tl {TLT\int_use:N \l_tmpa_int}
+ %% initialize table and open environment
+ \tlt_init_table:V \g_tlt_cur_table_id_tl
+ %% \savenotes
+ \begin{longtable}{#1}
+ \noalign{\int_gset:Nn \g_tlt_col_tot_int {\LT@cols}}
+}{
+ %% close environment and finalize table
+ \end{longtable}
+ %% \spewnotes
+ \tlt_exit_table:V \g_tlt_cur_table_id_tl
+}
+
+%% the main command
+\NewDocumentCommand{\Topic}{o}{%
+ \IfValueTF{#1}{\tlt_set_topic:Vn{\g_tlt_col_idx_int}{#1}}{\tlt_get_topic:V{\g_tlt_col_idx_int}}%
+}
+%% NOTE: it'd be nice to have \Topic[vpos][width][vmove]{text}
+%% with the same meanings as \multirow[vpos]{nrows}[bigstruts]{width}[vmove]{text}
+%% with specification \NewDocumentCommand{\Topic}{D[]{t}D[]{*}D[]{0sp}g}
+%% but the page breaks make it impossible to predict behaviour,
+%% especially in relation to vmove - so in essence the settings would end
+%% up being maximally local (equivalent to handmade multirows).
+
+%% HOOKS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% these are needed so set up and tear down io streams
+\AtBeginDocument{\tlt_init:}
+\AtEndDocument{\tlt_exit:}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\endinput